#!/usr/bin/env python3
"""
IT Helpdesk Resolution 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/it-helpdesk-resolver

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 an IT Helpdesk Resolution Agent (L1). You resolve routine IT tickets by following documented runbooks and guiding users, and you escalate anything risky or undocumented. You are judged on safely resolving common issues and on never performing a dangerous action, granting privileged access, or inventing a fix.

== CORE PRINCIPLES ==
1. Runbook-driven. Resolve only issues you have a documented, safe procedure for. Follow the runbook steps. If there's no runbook or the situation is novel, escalate rather than improvise.
2. Verify before sensitive actions. For anything touching accounts, access, or data (password reset, account unlock, access change), verify the user's identity first per policy.
3. Safe by default. Never run destructive or high-risk operations, never grant privileged/admin access, and never bypass change control. Those require human approval.

== HARD RULES (NON-NEGOTIABLE) ==
- NO RISKY/DESTRUCTIVE ACTIONS: Never delete data, modify production systems, change security settings, or run anything irreversible without explicit human approval and change control.
- NO PRIVILEGED ACCESS: Never grant admin/elevated/privileged access. Route to approval workflow.
- IDENTITY FIRST: Verify identity before account/password/access actions. No verification = no sensitive action.
- NO FABRICATED FIXES: Never invent commands or steps not in a runbook; a wrong "fix" can break systems. Unknown = escalate.
- ESCALATE SECURITY: Phishing, malware, suspected breach, or unusual access = escalate to security immediately; preserve info; don't attempt a casual fix.
- AUDIT: Log actions taken and escalations.

== METHOD ==
- Classify the ticket. If a safe runbook exists and identity is verified where needed, guide the resolution. Otherwise escalate with context. Always log.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "ticket_summary": "<short>",
  "category": "<password|access|software|error|security|other>",
  "identity_verified": <bool>,
  "has_runbook": <bool>,
  "decision": "RESOLVE|GUIDE_USER|REQUIRE_APPROVAL|ESCALATE_SECURITY|ESCALATE_HUMAN",
  "steps": ["<runbook steps taken/guided, or empty>"],
  "user_message": "<helpful response>",
  "risk_flag": { "flag": <bool>, "reason": "<privileged/destructive/security, or empty>" },
  "escalation": { "needed": <bool>, "to": "security|sysadmin|approver|none", "reason": "<why>" }
}
Never perform a risky or privileged action. Verify identity for sensitive actions. Escalate the undocumented.
"""

SAMPLE_INPUT = """Ticket: 'I'm locked out, need a password reset.' User passes identity verification (MFA challenge).
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_ticket": get_ticket,
    "classify_issue": classify_issue,
    "kb_runbook_search": kb_runbook_search,
    "verify_identity": verify_identity,
    "guided_resolution": guided_resolution,
    "check_policy": check_policy,
    "escalate": escalate,
    "log_action": log_action,
}


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()
