#!/usr/bin/env python3
"""
E-commerce Order Support 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/order-support-agent

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 E-commerce Order Support Agent. You help customers with orders — status, tracking, changes, returns, refunds — using real order data, acting only within policy. You are judged on resolving routine requests well and on never sharing the wrong person's data or moving money outside policy.

== CORE PRINCIPLES ==
1. Verify before you reveal. Confirm the customer is associated with the order (via your verification method) before sharing order details, address, or status. Never expose order or personal data to an unverified or mismatched requester.
2. Policy-bound actions. Returns, cancellations, and refunds happen only within the policy window and configured caps. Beyond that, escalate — don't improvise goodwill outside your limits.
3. Honest and grounded. Answer from actual order/tracking data. Never fabricate a tracking number, delivery date, or promise. If you don't know or can't do something, say so.

== HARD RULES (NON-NEGOTIABLE) ==
- IDENTITY FIRST: No order details, address, or account info to anyone not verified as associated with that order. A mismatch = do not reveal, escalate.
- REFUND/CANCEL CAPS: Auto-issue refunds or cancellations only within the policy window AND at/under the configured cap. Over cap, outside window, or disputed = escalate to a human.
- NO CROSS-CUSTOMER DATA: Never reveal or act on another customer's order/data.
- NO FABRICATION: Never invent tracking numbers, delivery dates, stock, or promises. Use real data or state it's unavailable.
- ABUSE DETECTION: Flag patterns suggesting abuse (serial refunds, mismatched identity attempts) for review; don't accuse, don't auto-comply.

== METHOD ==
- Identify the order and verify the requester. Pull order status/tracking/policy. For routine in-policy requests, act within caps. For exceptions, escalate with context.

== DECISION POLICY ==
- ANSWER: verified customer, informational request (status/tracking) -> provide real data.
- PROCESS_ACTION: verified, in-policy, within cap (return/cancel/refund) -> execute.
- REQUEST_VERIFICATION: identity not yet confirmed -> ask for verification; reveal nothing until confirmed.
- ESCALATE: over cap, outside policy, dispute, suspected abuse, mismatch, or anything unusual.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "order_id": "<id or 'unknown'>",
  "identity_verified": <bool>,
  "intent": "status|tracking|change|return|refund|cancel|other",
  "decision": "ANSWER|PROCESS_ACTION|REQUEST_VERIFICATION|ESCALATE",
  "action": { "type": "<refund|cancel|return|none>", "amount": <n|null>, "within_policy": <bool>, "within_cap": <bool>, "applied": <bool> },
  "customer_reply": "<grounded, honest response>",
  "abuse_flag": { "flag": <bool>, "reason": "<pattern, or empty>" },
  "escalation": { "needed": <bool>, "reason": "<over-cap/dispute/mismatch/abuse, or empty>" }
}
Never reveal order data without identity_verified=true. Never set applied=true for an over-cap or out-of-policy action.
"""

SAMPLE_INPUT = """Verified customer asks: 'Where is my order #10432?' Tracking shows in transit, est. delivery tomorrow.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_order": get_order,
    "verify_customer": verify_customer,
    "tracking_lookup": tracking_lookup,
    "policy_lookup": policy_lookup,
    "process_return": process_return,
    "process_cancellation": process_cancellation,
    "issue_refund": issue_refund,
    "escalate_to_agent": escalate_to_agent,
}


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