feat: govern chat model selection by policy
This commit is contained in:
@@ -29,6 +29,7 @@ ROOT = project_root()
|
||||
BIN = os.path.join(ROOT, "packages", "casan-harness", "scripts", "bash")
|
||||
ROUTER = os.path.join(BIN, "prompt-mode-router.py")
|
||||
AGENT_RESOLVER = os.path.join(BIN, "chat-agent-resolver.py")
|
||||
MODEL_RESOLVER = os.path.join(BIN, "chat-model-resolver.py")
|
||||
READONLY = os.path.join(BIN, "chat-readonly.py")
|
||||
OPERATOR = os.path.join(BIN, "chat-operator.py")
|
||||
APPROVAL_INBOX = os.path.join(BIN, "approval-inbox.py")
|
||||
@@ -112,6 +113,8 @@ def run_mode(args, binding, loop_run=None, scan_output_label=""):
|
||||
env = os.environ.copy()
|
||||
if loop_run is not None:
|
||||
env["CASAN_CHAT_LOOP_RUN_JSON"] = json.dumps(loop_run, ensure_ascii=False, sort_keys=True)
|
||||
if binding and binding.get("model_binding", {}).get("decision") == "BOUND":
|
||||
env["CASAN_CHAT_MODEL_PROVIDER"] = binding["model_binding"].get("provider", "")
|
||||
r = subprocess.run(args, cwd=ROOT, capture_output=True, text=True, env=env)
|
||||
if scan_output_label:
|
||||
scan_rc, scan_msg = scan_tool_output(r.stdout + "\n" + r.stderr, scan_output_label)
|
||||
@@ -144,8 +147,8 @@ def run_mode(args, binding, loop_run=None, scan_output_label=""):
|
||||
return r.returncode
|
||||
|
||||
|
||||
def run_and_passthrough(args):
|
||||
r = subprocess.run(args, cwd=ROOT, text=True)
|
||||
def run_and_passthrough(args, env=None):
|
||||
r = subprocess.run(args, cwd=ROOT, text=True, env=env)
|
||||
return r.returncode
|
||||
|
||||
|
||||
@@ -622,6 +625,20 @@ def bind_agent(args, router):
|
||||
return 2, None
|
||||
|
||||
|
||||
def bind_model(args, binding):
|
||||
r = subprocess.run([
|
||||
"python3", MODEL_RESOLVER, "bind",
|
||||
"--provider", args.model_provider,
|
||||
"--actor", args.actor,
|
||||
"--role", args.role,
|
||||
"--model-role", binding.get("model_role") or "read_only",
|
||||
], cwd=ROOT, capture_output=True, text=True)
|
||||
try:
|
||||
return r.returncode, json.loads(r.stdout)
|
||||
except Exception:
|
||||
return 2, {"success": False, "decision": "DENIED", "reason": "model_resolver_invalid_json"}
|
||||
|
||||
|
||||
def submit_escalation(args, router, binding):
|
||||
turn_id = args.turn_id or str(uuid.uuid4())
|
||||
payload = {
|
||||
@@ -695,6 +712,12 @@ def ask(args) -> int:
|
||||
if binding:
|
||||
print(json.dumps(binding, ensure_ascii=False))
|
||||
return bind_rc
|
||||
model_rc, model_binding = bind_model(args, binding)
|
||||
if model_rc != 0:
|
||||
print(json.dumps({"success": False, "mode": router.get("mode"), "decision": "DENIED", "reason": model_binding.get("reason"), "agent_binding": binding, "model_binding": model_binding}, ensure_ascii=False))
|
||||
return model_rc
|
||||
binding["model_binding"] = model_binding
|
||||
os.environ["CASAN_CHAT_MODEL_PROVIDER"] = model_binding.get("provider", "")
|
||||
common = [
|
||||
"--message", args.message,
|
||||
"--actor", args.actor,
|
||||
@@ -712,7 +735,9 @@ def ask(args) -> int:
|
||||
loop_run, loop_rc = certify_codegen_draft(args, router, binding)
|
||||
return finish_codegen(args, router, binding, loop_run, loop_rc)
|
||||
if getattr(args, "stream", False) and router.get("mode") in ("READ_ONLY", "ANALYSIS"):
|
||||
return run_and_passthrough(["python3", READONLY, "ask", "--stream", *common])
|
||||
env = os.environ.copy()
|
||||
env["CASAN_CHAT_MODEL_PROVIDER"] = model_binding.get("provider", "")
|
||||
return run_and_passthrough(["python3", READONLY, "ask", "--stream", *common], env)
|
||||
return run_mode(["python3", READONLY, "ask", *common], binding)
|
||||
|
||||
|
||||
@@ -740,6 +765,7 @@ def main() -> int:
|
||||
askp.add_argument("--tenant", default="default")
|
||||
askp.add_argument("--agent", default="")
|
||||
askp.add_argument("--skill", default="")
|
||||
askp.add_argument("--model-provider", default="")
|
||||
askp.add_argument("--delegation-level", type=int, default=0)
|
||||
askp.add_argument("--stream", action="store_true")
|
||||
askp.set_defaults(func=ask)
|
||||
|
||||
Reference in New Issue
Block a user