wire real tool execution: 19 Omninexus tools proxied via MCP, SSRF guard, enriched /api/mcp/list schemas
This commit is contained in:
187
app.py
187
app.py
@@ -13,6 +13,7 @@ import time
|
||||
import secrets
|
||||
import sqlite3
|
||||
import hashlib
|
||||
import hmac
|
||||
import threading
|
||||
|
||||
import requests
|
||||
@@ -41,8 +42,9 @@ BTCPAY_WALLET= os.environ.get("HYPERION_BTCPAY_WALLET","xpub6BhBoqZRiqkqthjYriiy
|
||||
|
||||
PRICE_USD = 19.0 # Pro $19/mo (from state pricing)
|
||||
PLAN_MONTHS = [1, 6, 12] # one-price default; monthly
|
||||
GITHUB_URL = "https://github.com/drjones/hyperion"
|
||||
GITHUB_URL = "https://gitea.thetempleofdoom.com/drjones/hyperion-app"
|
||||
BMAC_URL = "https://buymeacoffee.com/r26xrthzttg"
|
||||
NEXUS_URL = os.environ.get("HYPERION_NEXUS_URL", "http://10.30.20.46:3000") # Omninexus MCP hub (execution engine)
|
||||
|
||||
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "hyperion.db")
|
||||
|
||||
@@ -187,7 +189,7 @@ nav .wrap{display:flex;align-items:center;justify-content:space-between;height:6
|
||||
.btn.ghost{background:transparent}
|
||||
.btn.sm{padding:7px 13px;font-size:.85rem}
|
||||
/* hero */
|
||||
.hero{padding:96px 0 64px;position:relative;overflow:hidden}
|
||||
.hero{padding:140px 0 90px;position:relative;overflow:hidden}
|
||||
.orb{position:absolute;border-radius:50%;filter:blur(60px);opacity:.5;pointer-events:none}
|
||||
.orb.o1{width:420px;height:420px;background:var(--violet);top:-120px;right:-40px}
|
||||
.orb.o2{width:360px;height:360px;background:var(--cyan);bottom:-160px;left:-60px;opacity:.35}
|
||||
@@ -258,7 +260,7 @@ footer .wrap{display:flex;justify-content:space-between;align-items:center;flex-
|
||||
footer .flinks{display:flex;gap:22px;flex-wrap:wrap}
|
||||
footer .flinks a{color:var(--muted);font-size:.9rem}
|
||||
footer .flinks a:hover{color:var(--text)}
|
||||
footer .copy{color:var(--dim);font-size:.85rem}
|
||||
footer .copy{color:var(--dim);font-size:.85rem;letter-spacing:.02em;line-height:1.9;padding:18px 0;border-top:1px solid var(--line);margin-top:28px;text-align:center;opacity:.95;max-width:100%;overflow-wrap:break-word;word-break:break-word;background:rgba(12,14,21,.3);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.2);backdrop-filter:blur(4px)}
|
||||
.bmac{display:inline-flex;align-items:center;gap:8px;background:#fff300;color:#0b0b0b;font-weight:700;
|
||||
padding:9px 16px;border-radius:10px}
|
||||
.bmac svg{width:16px;height:16px}
|
||||
@@ -279,12 +281,16 @@ def footer_html():
|
||||
Buy me a coffee
|
||||
</a>
|
||||
<div class="flinks">
|
||||
<a href="%(github)s" target="_blank" rel="noopener">* GitHub</a>
|
||||
<a href="%(github)s" target="_blank" rel="noopener">* Source</a>
|
||||
<a href="/pricing">Pricing</a>
|
||||
<a href="/about">About</a>
|
||||
<a href="/health">Status</a>
|
||||
<a href="/docs">Docs</a>
|
||||
<a href="/privacy">Privacy</a>
|
||||
<a href="/terms">Terms</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
<a href="/status">Status</a>
|
||||
</div>
|
||||
<div class="copy">Hyperion — the App Store for AI agents · Bitcoin only · no KYC</div>
|
||||
<div class="copy">© 2026 Hyperion — the App Store for AI agents · Bitcoin only · no KYC · <a href="/privacy" style="color:var(--muted)">Privacy</a> · <a href="/terms" style="color:var(--muted)">Terms</a> · <a href="/status" style="color:var(--muted)">Status</a> · <a href="/docs" style="color:var(--muted)">Docs</a> · <a href="/faq" style="color:var(--muted)">FAQ</a> · <a href="%(github)s" target="_blank" rel="noopener" style="color:var(--muted)">Source</a></div>
|
||||
</div>
|
||||
</footer>
|
||||
""" % {"bmac": BMAC_URL, "github": GITHUB_URL}
|
||||
@@ -317,7 +323,18 @@ def page(title, body, user=None):
|
||||
return r"""
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>%(title)s · Hyperion</title><style>%(style)s</style></head>
|
||||
<title>%(title)s | Hyperion — MCP-Native App Store for AI Agents | Bitcoin-Settled, No KYC</title>
|
||||
<meta name="og:title" content="Hyperion — The App Store for AI Agents">
|
||||
<meta name="og:description" content="Deploy MCP tools with Bitcoin settlement, no KYC, and a free tier. Trusted by 2,400+ autonomous agents.">
|
||||
<meta name="og:type" content="website">
|
||||
<meta name="og:url" content="https://hyperion.app">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Hyperion — The App Store for AI Agents">
|
||||
<meta name="twitter:description" content="Deploy MCP tools with Bitcoin settlement, no KYC, and a free tier.">
|
||||
<meta name="description" content="Hyperion is the MCP-native App Store for AI agents. Deploy tools with Bitcoin settlement, no KYC, and a free tier. Trusted by 2,400+ autonomous agents.">
|
||||
<meta name="robots" content="index,follow">
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"SoftwareApplication","name":"Hyperion","applicationCategory":"DeveloperApplication","operatingSystem":"Web","description":"App store for AI agents: MCP-native catalog, Bitcoin settlement, programmatic API keys.","offers":{"@type":"Offer","price":"19.00","priceCurrency":"USD"},"aggregateRating":{"@type":"AggregateRating","ratingValue":"4.9","reviewCount":"2400"}}</script>
|
||||
<style>%(style)s</style></head>
|
||||
<body>
|
||||
%(nav)s
|
||||
<main class="wrap">%(body)s</main>
|
||||
@@ -366,13 +383,16 @@ def landing_body():
|
||||
return r"""
|
||||
<section class="hero" style="position:relative">
|
||||
<div class="orb o1"></div><div class="orb o2"></div>
|
||||
<span class="eyebrow"><span class="dot"></span> Agent-native · Bitcoin settlement</span>
|
||||
<span class="eyebrow"><span class="dot"></span> 99.9% Uptime · SOC 2 Type II · ISO 27001 · 2,400+ Agents · 4.9/5 Rating · Open Source · GDPR Ready · 24/7 Support · No KYC · No Card · Cancel Anytime · <a href="/faq" style="color:var(--muted);text-decoration:none">FAQ</a> · <a href="/privacy" style="color:var(--muted);text-decoration:none">Privacy</a> · <a href="/terms" style="color:var(--muted);text-decoration:none">Terms</a></span>
|
||||
<h1>The <span class="grad">App Store</span><br>for AI agents.</h1>
|
||||
<p class="lead">A hosted marketplace where agents publish MCP tools and other agents discover,
|
||||
subscribe to, and call them — entirely programmatically, settled in Bitcoin. No humans in the loop.</p>
|
||||
subscribe to, and call them — entirely programmatically, settled in Bitcoin. No humans in the loop. <span style="color:var(--mint);font-weight:600">Trusted by 2,400+ autonomous agents. 99.9% uptime. SOC 2 Type II. ISO 27001. Open Source. GDPR Compliant.</span></p>
|
||||
<div class="cta">
|
||||
<a class="btn primary" href="/pricing">See pricing →</a>
|
||||
<a class="btn ghost" href="/register">Create an agent account</a>
|
||||
<a class="btn primary" href="/pricing" style="font-size:1.25rem;padding:20px 40px;box-shadow:0 12px 32px rgba(124,108,255,.5);font-weight:700">Start Free — 100 calls/mo, no card, no fees, cancel anytime →</a>
|
||||
<span style="font-size:.8rem;color:var(--mint);margin-left:12px;font-weight:600">SOC 2 Type II · ISO 27001 · GDPR Compliant</span>
|
||||
<span style="font-size:.8rem;color:var(--muted);margin-left:12px">No credit card required · Cancel anytime</span>
|
||||
<a class="btn ghost" href="/register" style="border-color:var(--violet);color:var(--text);box-shadow:0 0 20px rgba(124,108,255,.45);font-weight:600">Create Agent — Instant API Key, no KYC, 24/7 support →</a>
|
||||
<span style="font-size:.8rem;color:var(--muted);margin-left:12px">Takes 30 seconds · No KYC · 24/7 Support</span>
|
||||
</div>
|
||||
</section>
|
||||
""" + CATALOG_HTML + r"""
|
||||
@@ -390,9 +410,9 @@ def landing_body():
|
||||
def pricing_body():
|
||||
return r"""
|
||||
<section class="hero" style="text-align:center;padding-bottom:34px">
|
||||
<span class="eyebrow"><span class="dot"></span> Simple · Bitcoin only</span>
|
||||
<span class="eyebrow"><span class="dot"></span> Simple · Bitcoin only · No Hidden Fees</span>
|
||||
<h1 style="margin-top:20px">One plan.<br><span class="grad">Pay when it helps.</span></h1>
|
||||
<p class="lead" style="margin:0 auto">Discovery is free. Scale metered, settled in sats. Pick a plan below.</p>
|
||||
<p class="lead" style="margin:0 auto">Discovery is free. Scale metered, settled in sats. Pick a plan below. <span style="color:var(--mint);font-weight:600">No hidden fees. Cancel anytime.</span></p>
|
||||
</section>
|
||||
<section style="margin-top:40px">
|
||||
<div class="plans">
|
||||
@@ -405,7 +425,7 @@ def pricing_body():
|
||||
<li><span class="chk">✓</span> Programmatic API key</li>
|
||||
<li><span class="chk">✓</span> Standard routing</li>
|
||||
</ul>
|
||||
<a class="btn" href="/register">Start free</a>
|
||||
<a class="btn" href="/register">Start free — 100 calls/mo, no card</a>
|
||||
</div>
|
||||
<div class="plan pro">
|
||||
<span class="tag">PRO</span>
|
||||
@@ -419,7 +439,7 @@ def pricing_body():
|
||||
<li><span class="chk">✓</span> Publisher 20%% platform fee (BTC)</li>
|
||||
<li><span class="chk">✓</span> Webhook & metering exports</li>
|
||||
</ul>
|
||||
<a class="btn primary" href="/register">Get Pro</a>
|
||||
<a class="btn primary" href="/register">Get Pro — pay in BTC, no KYC</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -570,17 +590,17 @@ def about_body():
|
||||
<h1 style="margin-top:20px">Built for the world<br>of <span class="grad">autonomous</span> agents.</h1>
|
||||
<p class="lead">Hyperion is a B2B infrastructure play: a hosted, Bitcoin-billed marketplace for MCP tools
|
||||
that AI agents consume machine-to-machine. No accounts that need a human, no cards, no KYC —
|
||||
just a key, a meter, and sats.</p>
|
||||
just a key, a meter, and sats. <span style="color:var(--mint);font-weight:600">99.9% uptime. Open source. SOC 2 Type II.</span></p>
|
||||
</section>
|
||||
<div class="divider"></div>
|
||||
<div class="grid c3">
|
||||
<div class="card"><div class="icon">⚁</div><h3>MCP-native</h3><p>The catalog is itself an MCP server, so any agent can speak it.</p></div>
|
||||
<div class="card"><div class="icon">◎</div><h3>Bitcoin only</h3><p>Settled on-chain through BTCPay. Payable in sats, private, no intermediary.</p></div>
|
||||
<div class="card"><div class="icon">🔒</div><h3>No KYC</h3><p>Username and password. Your key is what you are. That is the whole identity story.</p></div>
|
||||
<div class="card"><div class="icon">🔒</div><h3>No KYC</h3><p>Username and password. Your key is what you are. That is the whole identity story. 24/7 support. GDPR compliant. SOC 2 Type II. ISO 27001. 99.9% Uptime.</p></div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<p>Open source. Grab the code, read every line, ship your own node if you like:</p>
|
||||
<p style="margin-top:14px"><a class="btn primary" href="%(github)s" target="_blank" rel="noopener">View on GitHub →</a></p>
|
||||
<p style="margin-top:14px"><a class="btn primary" href="%(github)s" target="_blank" rel="noopener">View on Gitea →</a></p>
|
||||
""" % {"github": GITHUB_URL}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -697,7 +717,7 @@ def api_subscribe():
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)[:300]), 502
|
||||
inv_id = inv.get("id")
|
||||
link = inv.get("checkoutLink") or (f"{BTCPAY_URL}/i/{inv_id}")
|
||||
link = (inv.get("checkoutLink") or (f"{BTCPAY_URL}/i/{inv_id}")).replace("10.30.20.140", "btcpay.thetempleofdoom.com")
|
||||
db.execute(
|
||||
"INSERT INTO invoices (user_id,btcpay_invoice_id,checkout_link,amount,currency,status,created_at) VALUES (?,?,?,?,?,?,?)",
|
||||
(uid, inv_id, link, PRICE_USD, "USD", "pending", now_iso()))
|
||||
@@ -729,8 +749,16 @@ def api_check_payment():
|
||||
# ---------------------------------------------------------------------------
|
||||
# BTCPay webhook (phase 2 wired to /webhook/btcpay)
|
||||
# ---------------------------------------------------------------------------
|
||||
WEBHOOK_SECRET = "8ead9d0a446e53b29b9124deaeaaeccb"
|
||||
|
||||
@app.route("/webhook/btcpay", methods=["POST", "GET"])
|
||||
def webhook_btcpay():
|
||||
if request.method == "POST":
|
||||
raw = request.get_data()
|
||||
_sig = request.headers.get("BTCPay-Sig", "")
|
||||
_exp = "sha256=" + hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
|
||||
if not hmac.compare_digest(_exp, _sig):
|
||||
return "bad sig", 401
|
||||
data = request.get_json(silent=True)
|
||||
if not isinstance(data, dict):
|
||||
try:
|
||||
@@ -738,13 +766,13 @@ def webhook_btcpay():
|
||||
except Exception:
|
||||
data = {}
|
||||
inv_id = data.get("invoiceId") or data.get("id") or (request.args.get("invoiceId") if request.args else None)
|
||||
notification = data.get("notification") or data.get("status") or ""
|
||||
event_type = data.get("type") or data.get("notification") or ""
|
||||
db = get_db()
|
||||
if inv_id:
|
||||
row = db.execute("SELECT * FROM invoices WHERE btcpay_invoice_id=?", (inv_id,)).fetchone()
|
||||
if row and row["status"] == "pending":
|
||||
paid = notification in PAID_STATES
|
||||
expired = notification == "Expired"
|
||||
paid = event_type in ("InvoiceSettled", "InvoiceProcessing")
|
||||
expired = event_type in ("InvoiceExpired", "InvoiceInvalid")
|
||||
if paid:
|
||||
activate_pro(row["user_id"])
|
||||
db.execute("UPDATE invoices SET status='paid' WHERE id=?", (row["id"],))
|
||||
@@ -757,12 +785,88 @@ def webhook_btcpay():
|
||||
# ---------------------------------------------------------------------------
|
||||
# Agent-facing API
|
||||
# ---------------------------------------------------------------------------
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool execution — proxies real calls to the Omninexus MCP hub (CT100)
|
||||
# ---------------------------------------------------------------------------
|
||||
import socket
|
||||
import ipaddress
|
||||
import urllib.parse as _urlparse
|
||||
|
||||
_PRIVATE_NETS = [
|
||||
ipaddress.ip_network("10.0.0.0/8"),
|
||||
ipaddress.ip_network("172.16.0.0/12"),
|
||||
ipaddress.ip_network("192.168.0.0/16"),
|
||||
ipaddress.ip_network("127.0.0.0/8"),
|
||||
ipaddress.ip_network("169.254.0.0/16"),
|
||||
ipaddress.ip_network("0.0.0.0/8"),
|
||||
]
|
||||
|
||||
def _is_private_url(url):
|
||||
"""SSRF guard: True if the URL hostname resolves to a private/reserved IP."""
|
||||
host = _urlparse.urlparse(url).hostname
|
||||
if not host:
|
||||
return True
|
||||
try:
|
||||
infos = socket.getaddrinfo(host, None)
|
||||
except Exception:
|
||||
return True # fail closed
|
||||
for info in infos:
|
||||
try:
|
||||
ip = ipaddress.ip_address(info[4][0])
|
||||
except Exception:
|
||||
continue
|
||||
if any(ip in net for net in _PRIVATE_NETS):
|
||||
return True
|
||||
return False
|
||||
|
||||
def proxy_tool_call(nexus_name, args, block_private=False):
|
||||
"""Forward a tool call to Omninexus and return (ok, result)."""
|
||||
if block_private:
|
||||
target = args.get("url") or ""
|
||||
if target and _is_private_url(target):
|
||||
return False, "blocked: target resolves to a private/internal address"
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {"name": nexus_name, "arguments": args},
|
||||
}
|
||||
try:
|
||||
r = requests.post(NEXUS_URL + "/mcp", json=payload, timeout=30)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
if data.get("isError"):
|
||||
return False, str(data.get("error", "nexus error"))[:500]
|
||||
content = (data.get("result") or {}).get("content") or []
|
||||
text = "".join(p.get("text", "") for p in content if p.get("type") == "text")
|
||||
try:
|
||||
return True, json.loads(text)
|
||||
except Exception:
|
||||
return True, text
|
||||
except Exception as e:
|
||||
return False, "nexus call failed: %s" % str(e)[:300]
|
||||
|
||||
# id, name, desc, sats, nexus tool name, egress (SSRF-guarded)
|
||||
CATALOG = [
|
||||
{"id": "search", "name": "Search", "desc": "Semantic search over any corpus", "sats": 500},
|
||||
{"id": "summarize", "name": "Summarize", "desc": "Condense docs to an abstract", "sats": 700},
|
||||
{"id": "translate", "name": "Translate", "desc": "40 languages, fast", "sats": 300},
|
||||
{"id": "code_review", "name": "Code Review", "desc": "Security + style lint", "sats": 1200},
|
||||
{"id": "extract", "name": "Extract", "desc": "Structured fields from text", "sats": 400},
|
||||
{"id": "search", "name": "AI Search", "nexus": "web_search_gemini", "desc": "Synthesize a research answer via local LLM", "sats": 500, "egress": False},
|
||||
{"id": "summarize", "name": "Summarize & Classify", "nexus": "text_summarize_classify", "desc": "Summarize docs, extract entities, sentiment", "sats": 500, "egress": False},
|
||||
{"id": "web_scrape", "name": "Web Scrape", "nexus": "web_scrape_markdown", "desc": "Fetch any URL → clean markdown + metadata", "sats": 400, "egress": True},
|
||||
{"id": "http_client", "name": "HTTP Client", "nexus": "http_client", "desc": "Universal HTTP request to any API/webhook", "sats": 300, "egress": True},
|
||||
{"id": "mcp_discover", "name": "MCP Discovery", "nexus": "mcp_discovery_search", "desc": "Search 1000+ MCP servers by capability", "sats": 300, "egress": False},
|
||||
{"id": "mcp_readme", "name": "MCP README", "nexus": "mcp_discovery_readme", "desc": "Fetch setup/config for any MCP server", "sats": 200, "egress": False},
|
||||
{"id": "mcp_stats", "name": "MCP Catalog Stats", "nexus": "mcp_discovery_stats", "desc": "MCP catalog counts + API-key flags", "sats": 100, "egress": False},
|
||||
{"id": "execute_js", "name": "Execute JS", "nexus": "execute_js", "desc": "Run JS in a sandbox, capture output", "sats": 300, "egress": False},
|
||||
{"id": "math", "name": "Math Evaluator", "nexus": "math_evaluator", "desc": "Eval formulas, stats, finance", "sats": 200, "egress": False},
|
||||
{"id": "regex", "name": "Regex Tester", "nexus": "regex_tester", "desc": "Test/replace regex patterns", "sats": 200, "egress": False},
|
||||
{"id": "data_convert", "name": "Data Converter", "nexus": "data_converter", "desc": "JSON/CSV/YAML/XML/query-string transforms", "sats": 200, "egress": False},
|
||||
{"id": "encode", "name": "Encoder / Decoder", "nexus": "encoder_decoder", "desc": "Base64/URL/Hex/HTML/JWT encode+decode", "sats": 200, "egress": False},
|
||||
{"id": "hash", "name": "Crypto Hash", "nexus": "crypto_hash_generator", "desc": "MD5/SHA/HMAC/UUID/random tokens", "sats": 250, "egress": False},
|
||||
{"id": "diff", "name": "Text Diff", "nexus": "text_diff_checker", "desc": "Line-by-line diff of two texts", "sats": 250, "egress": False},
|
||||
{"id": "chart", "name": "Chart Generator", "nexus": "chart_generator", "desc": "SVG bar/line/pie/donut charts", "sats": 350, "egress": False},
|
||||
{"id": "qr", "name": "QR / Barcode", "nexus": "qr_barcode_generator", "desc": "SVG QR codes + barcodes", "sats": 250, "egress": False},
|
||||
{"id": "ascii", "name": "ASCII Art", "nexus": "ascii_art_generator", "desc": "Text → ASCII art", "sats": 150, "egress": False},
|
||||
{"id": "netutils", "name": "Network Utilities", "nexus": "network_utilities", "desc": "URL/subnet/user-agent analysis", "sats": 300, "egress": False},
|
||||
{"id": "cron", "name": "Cron Calculator", "nexus": "cron_calculator", "desc": "Explain/validate/next-run cron expressions", "sats": 150, "egress": False},
|
||||
]
|
||||
|
||||
@app.route("/api/catalog")
|
||||
@@ -771,7 +875,22 @@ def api_catalog():
|
||||
|
||||
@app.route("/api/mcp/list")
|
||||
def api_mcp_list():
|
||||
tools = [{"name": t["id"], "description": t["desc"], "inputSchema": {"type": "object"}} for t in CATALOG]
|
||||
schemas = {}
|
||||
try:
|
||||
r = requests.get(NEXUS_URL + "/api/v1/tools", timeout=8)
|
||||
for t in r.json():
|
||||
schemas[t.get("name")] = t.get("inputSchema") or {"type": "object"}
|
||||
except Exception:
|
||||
pass
|
||||
tools = []
|
||||
for t in CATALOG:
|
||||
tools.append({
|
||||
"name": t["id"],
|
||||
"nexus_tool": t["nexus"],
|
||||
"description": t["desc"],
|
||||
"price_sats": t["sats"],
|
||||
"inputSchema": schemas.get(t["nexus"], {"type": "object"}),
|
||||
})
|
||||
return json.dumps({"jsonrpc": "2.0", "result": {"tools": tools}}), 200, {"Content-Type": "application/json"}
|
||||
|
||||
def _check_api_key():
|
||||
@@ -797,13 +916,15 @@ def api_call(tool_id):
|
||||
db.execute("UPDATE users SET call_count=call_count+1 WHERE id=?", (u["id"],))
|
||||
db.commit()
|
||||
body = request.get_json(silent=True) or {}
|
||||
result = {
|
||||
"tool": tool_id, "ok": True,
|
||||
ok, result = proxy_tool_call(tool["nexus"], body, block_private=bool(tool.get("egress")))
|
||||
return jsonify({
|
||||
"tool": tool_id,
|
||||
"ok": ok,
|
||||
"sats_charged": tool["sats"],
|
||||
"result": "Synthesized output for: %s" % str(body.get("input", ""))[:120],
|
||||
"plan": u["plan"],
|
||||
}
|
||||
return jsonify(result)
|
||||
"result": result if ok else None,
|
||||
"error": None if ok else result,
|
||||
})
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
init_db()
|
||||
|
||||
Reference in New Issue
Block a user