forked from Jason-Vaughan/TangleBrain
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathviews.py
More file actions
152 lines (122 loc) · 5.94 KB
/
Copy pathviews.py
File metadata and controls
152 lines (122 loc) · 5.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""View functions for the knob GUI — pure, transport-free, JSON-able dict builders.
These hold all the panel's logic so it can be unit-tested without binding a socket or making a
network call (mirroring how :mod:`tanglebrain.cli` is a thin wrapper over ``run_once``). The HTTP
layer in :mod:`tanglebrain.gui.server` only routes requests to these and serializes the result.
Read-only: nothing here writes config. Secret-safety — :func:`view_roster` emits ``key_ref`` as the
stored *reference string* only (e.g. ``file:…``, ``env:NAME``); it never resolves the reference or
reads a key file, so no secret material can reach the browser.
"""
from __future__ import annotations
from tanglebrain.adapters import AdapterError
from tanglebrain.cli import run_once
from tanglebrain.measurement import load_pricing, read_records, rollup, save_pricing, validate_pricing
from tanglebrain.roster import RosterError, load_roster
from tanglebrain.router import RouterError
from tanglebrain.selector import SelectionError
from tanglebrain.settings import load_settings
# Default port for the panel; registered permanently in TangleClaw PortHub for project TangleBrain.
DEFAULT_PORT = 3250
# Exceptions that represent an expected, user-facing failure of a run (mirrors cli.main()).
_RUN_ERRORS = (RosterError, SelectionError, RouterError, AdapterError)
def view_roster() -> dict:
"""Build the roster view: every entry with its tier, cost, tags, and invoke summary.
``key_ref`` is passed through verbatim as the reference string — never resolved, so no key
file contents are read or exposed. ``cmd``/``scrub_env``/``delegate_args`` are deliberately
omitted (not needed for the panel and keep the payload focused).
Returns:
``{"entries": [ {id, tier, cost, good_at, can_orchestrate, enabled, budget_usd_month,
invoke{...}}, ... ]}``. ``enabled`` / ``budget_usd_month`` matter for ``tier: api`` entries
(issue #2): ``enabled`` is the per-key kill-switch and ``budget_usd_month`` a display-only
cap (enforced LiteLLM-side). Whether paid entries are actually routable also depends on the
global gate — see :func:`view_settings`.
"""
roster = load_roster()
entries = []
for e in roster.entries:
entries.append(
{
"id": e.id,
"tier": e.tier,
"cost": e.cost,
"good_at": list(e.good_at),
"can_orchestrate": e.can_orchestrate,
"enabled": e.enabled,
"budget_usd_month": e.budget_usd_month,
"invoke": {
"kind": e.invoke.kind,
"base_url": e.invoke.base_url,
"model": e.invoke.model,
"parse": e.invoke.parse,
# Reference string only — see module docstring. Never resolved.
"key_ref": e.invoke.key_ref,
},
}
)
return {"entries": entries}
def view_settings() -> dict:
"""Build the global-settings view — the paid-API billing gate (issue #2).
``api_billing_enabled`` is the master switch (plan §9.6): when ``false`` (the default) no
``tier: api`` entry is routable regardless of its own ``enabled`` flag. The panel surfaces it so
an operator sees at a glance whether paid routing is live. Reads only ``config/settings.yaml`` —
no key file or secret is touched.
Returns:
``{"api_billing_enabled": bool}``.
"""
return {"api_billing_enabled": load_settings().api_billing_enabled}
def view_pricing() -> dict:
"""Build the pricing view from ``config/pricing.yaml``.
Returns:
``{reference_model, input_per_mtok, output_per_mtok, is_placeholder}``.
"""
p = load_pricing()
return {
"reference_model": p.reference_model,
"input_per_mtok": p.input_per_mtok,
"output_per_mtok": p.output_per_mtok,
"is_placeholder": p.is_placeholder,
}
def view_stats() -> dict:
"""Build the spend-avoided rollup view (the local C4 ``--stats`` data).
Returns:
``{summary, pricing_ref, is_placeholder}`` where ``summary`` is :func:`rollup`'s dict.
"""
pricing = load_pricing()
return {
"summary": rollup(read_records()),
"pricing_ref": pricing.reference_model,
"is_placeholder": pricing.is_placeholder,
}
def run_prompt(payload: dict) -> dict:
"""Run one prompt through the router and report the result + which tier served it.
Args:
payload: ``{prompt, task?, local?, model?}`` from the panel's run box.
Returns:
``{"ok": True, "text": ..., "served": {path, tier, model} | None}`` on success, or
``{"ok": False, "error": ...}`` on an empty prompt or an expected backend error.
"""
prompt = (payload or {}).get("prompt")
if not prompt or not str(prompt).strip():
return {"ok": False, "error": "prompt is required"}
task = payload.get("task") or None
model = payload.get("model") or None
local = bool(payload.get("local", False))
try:
# return_served gives us the served tier/model directly — no usage-log re-read, no race.
text, served = run_once(str(prompt), model=model, local=local, task=task, return_served=True)
except _RUN_ERRORS as exc:
return {"ok": False, "error": str(exc)}
return {"ok": True, "text": text, "served": served}
def save_pricing_view(payload: dict) -> dict:
"""Validate and persist edited pricing from the panel.
Args:
payload: ``{reference_model, input_per_mtok, output_per_mtok, placeholder}``.
Returns:
``{"ok": True, "pricing": {...}}`` with the re-read pricing on success, or
``{"ok": False, "error": ...}`` if validation fails (nothing is written on failure).
"""
try:
pricing = validate_pricing(payload or {})
except ValueError as exc:
return {"ok": False, "error": str(exc)}
save_pricing(pricing)
return {"ok": True, "pricing": view_pricing()}