#!/usr/bin/env python3
"""
Phishing Triage & Response Agent — runnable demo (Agent Blueprint from AgentKits)

This runs a REAL agent loop: it sends the kit's actual system prompt and a
realistic sample input to the model, prints the structured decision, and then
executes any proposed actions against MOCK tools (labelled [MOCK]).

The model/reasoning is real. The tools are stubs that return fake data and do
NOT touch any real system. Wiring the mock tools to your own integrations
(CRM, database, GitHub, etc.) is the final implementation step — see README.md.

Works with Anthropic OR OpenAI — it auto-detects whichever API key is set.

© 2026 AgentKits · www.agent-kits.com · Licensed CC BY 4.0 (attribution required)
Original blueprint: https://agent-kits.com/kit/phishing-report-analyzer

Setup (pick one):
    pip install anthropic   &&  export ANTHROPIC_API_KEY=sk-ant-...
    pip install openai      &&  export OPENAI_API_KEY=sk-...
Then:
    python run.py
"""
import os, json, sys

ANTHROPIC_MODEL = "claude-sonnet-4-6"
OPENAI_MODEL = "gpt-4o"

SYSTEM_PROMPT = """You are a Phishing Triage Agent handling one user-reported suspicious email. Your job is to determine what it is, scope its spread, and respond safely — or escalate. You are judged on catching real phishing (never clearing a true threat), cutting false alarms, and never taking an unsafe analysis or response action.

== CORE PRINCIPLES ==
1. Evidence-based verdict. Base your classification on what you gathered — sender authentication (SPF/DKIM/DMARC), URL/domain reputation, sandbox detonation results, header anomalies, and content signals. Cite them. Never guess 'safe'.
2. Analyze safely. Detonate links and attachments ONLY in the sandbox. Never fetch, click, or open a suspect URL/attachment in the live environment, and never echo live malicious payloads.
3. Scope before you respond. A reported email is often one of many. Find the campaign so response covers everyone affected, not just the reporter.

== HARD RULES (NON-NEGOTIABLE) ==
- SANDBOX ONLY: All detonation/analysis of URLs and attachments happens in the sandbox. No live interaction with malicious infrastructure.
- MASS ACTION NEEDS APPROVAL: You may auto-quarantine the reported message and a tightly-scoped, high-confidence campaign on non-critical mailboxes. Org-wide purge, action affecting executives/critical mailboxes, or anything large-blast-radius REQUIRES human approval — propose it.
- NEVER FALSE-CLEAR: Mark an email 'safe' only with positive evidence (auth pass + known-good sender + clean indicators). Mixed or insufficient evidence is 'suspicious' → escalate, not cleared.
- BEC / SPEAR-PHISH → ESCALATE: Targeted impersonation (executive, vendor, wire/payment request), even with few classic indicators, is high-risk. Do not auto-classify-and-close; escalate to the SOC and warn about the requested action (e.g. wire).
- DATA HANDLING: Treat email content as sensitive; redact credentials/PII; stay within scope.

== RESPONSE POLICY (calibrated confidence 0.0-1.0) ==
- AUTO_CONTAIN: confirmed phishing/malicious, confidence >= 0.85, scoped to non-critical mailboxes. Quarantine the campaign, block indicators (URL/domain/sender), notify reporter.
- CLEAR (safe): positive benign evidence, confidence >= 0.85. Reassure the reporter; no action.
- PROPOSE: real but large-blast-radius response (org-wide purge, exec mailboxes). Recommend with evidence for one-click approval.
- ESCALATE: BEC/spear-phish, credential-harvest where users may have already entered creds, conflicting evidence, or confidence < 0.6.

== COST CONTROL ==
Enrich only the indicators that change the verdict; reuse results already gathered. One good detonation beats many redundant lookups. Cap tool calls; if exceeded, escalate with what you have.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "verdict": "phishing|malicious|spam|safe|suspicious",
  "confidence": <0.0-1.0>,
  "evidence": ["<auth/reputation/sandbox/header signals>"],
  "campaign": "<scope: how many recipients / similar messages, or 'single'>",
  "decision": "AUTO_CONTAIN|CLEAR|PROPOSE|ESCALATE",
  "actions": [ { "tool": "<tool>", "args": { ... }, "requires_approval": <bool> } ],
  "reporter_reply": "<short, clear message to the user who reported it>",
  "analyst_note": "<summary + cited evidence + any user-impact note>",
  "escalation": { "needed": <bool>, "to": "soc|none", "reason": "<why, or empty>" }
}
If verdict is suspicious or evidence is mixed, do NOT CLEAR — ESCALATE or PROPOSE.
"""

SAMPLE_INPUT = """Reported email: 'Your mailbox is full, re-validate here' from no-reply@micros0ft-login.com, link to hxxps://micros0ft-login.com/verify.
"""

# ---- MOCK tools (stubs — replace with real integrations to deploy) ----
def get_reported_email(args=None):
    print(f"  [MOCK] get_reported_email(" + str(args) + ")")
    return {"status": "mock", "tool": "get_reported_email"}

def sender_auth_check(args=None):
    print(f"  [MOCK] sender_auth_check(" + str(args) + ")")
    return {"status": "mock", "tool": "sender_auth_check"}

def url_reputation(args=None):
    print(f"  [MOCK] url_reputation(" + str(args) + ")")
    return {"status": "mock", "tool": "url_reputation"}

def detonate_sandbox(args=None):
    print(f"  [MOCK] detonate_sandbox(" + str(args) + ")")
    return {"status": "mock", "tool": "detonate_sandbox"}

def search_campaign(args=None):
    print(f"  [MOCK] search_campaign(" + str(args) + ")")
    return {"status": "mock", "tool": "search_campaign"}

def quarantine_email(args=None):
    print(f"  [MOCK] quarantine_email(" + str(args) + ")")
    return {"status": "mock", "tool": "quarantine_email"}

def block_indicator(args=None):
    print(f"  [MOCK] block_indicator(" + str(args) + ")")
    return {"status": "mock", "tool": "block_indicator"}

def escalate_to_soc(args=None):
    print(f"  [MOCK] escalate_to_soc(" + str(args) + ")")
    return {"status": "mock", "tool": "escalate_to_soc"}

TOOLS = {
    "get_reported_email": get_reported_email,
    "sender_auth_check": sender_auth_check,
    "url_reputation": url_reputation,
    "detonate_sandbox": detonate_sandbox,
    "search_campaign": search_campaign,
    "quarantine_email": quarantine_email,
    "block_indicator": block_indicator,
    "escalate_to_soc": escalate_to_soc,
}


def call_model(system, user):
    """Dispatch to whichever provider's key is configured."""
    if os.environ.get("ANTHROPIC_API_KEY"):
        try:
            from anthropic import Anthropic
        except ImportError:
            sys.exit("Run: pip install anthropic")
        msg = Anthropic().messages.create(
            model=ANTHROPIC_MODEL,
            max_tokens=1600,
            system=system,
            messages=[{"role": "user", "content": user}],
        )
        return "".join(b.text for b in msg.content if getattr(b, "type", "") == "text")
    if os.environ.get("OPENAI_API_KEY"):
        try:
            from openai import OpenAI
        except ImportError:
            sys.exit("Run: pip install openai")
        resp = OpenAI().chat.completions.create(
            model=OPENAI_MODEL,
            max_tokens=1600,
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
        )
        return resp.choices[0].message.content
    sys.exit("Set ANTHROPIC_API_KEY or OPENAI_API_KEY first.")


def extract_json(text):
    a, b = text.find("{"), text.rfind("}")
    if a == -1 or b == -1:
        return None
    try:
        return json.loads(text[a : b + 1])
    except json.JSONDecodeError:
        return None


def main():
    print("=" * 70)
    print("INPUT:\n" + SAMPLE_INPUT)
    print("=" * 70)

    text = call_model(SYSTEM_PROMPT, SAMPLE_INPUT)
    print("AGENT DECISION:\n" + text)
    print("=" * 70)

    data = extract_json(text)
    if not data:
        print("(No JSON object parsed — the model returned prose.)")
        return

    actions = data.get("actions") or []
    if actions:
        print("EXECUTING PROPOSED ACTIONS (mock):")
        for a in actions:
            name = a.get("tool")
            args = a.get("args", {})
            if name in TOOLS:
                TOOLS[name](args)
            else:
                print(f"  [skip] no mock tool for '{name}'")
    else:
        print("This blueprint returned a structured result (no side-effecting "
              "actions in this sample). Available mock tools: "
              + ", ".join(TOOLS.keys()))


if __name__ == "__main__":
    main()
