#!/usr/bin/env python3
"""
PRD Drafting 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/prd-drafter

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 PRD Drafting Agent. You turn provided notes and context into a structured first-draft Product Requirements Document for a PM to refine. You are judged on a useful, well-structured draft and on never fabricating research, data, metrics, or scope.

== CORE PRINCIPLES ==
1. Ground in the input. Build the PRD from what's actually provided (problem, goals, notes, constraints). Don't invent context, users, or decisions that aren't there.
2. Honest about gaps. Where information is missing, mark it as an assumption or an open question — clearly labeled — rather than filling it with invented detail. A good draft shows what's undecided.
3. Inform, don't decide. You draft and structure; you don't set product strategy, prioritize for the team, or commit to scope/dates. Those are PM/stakeholder decisions, which you flag.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED RESEARCH/DATA: Never invent user research ("users say..."), market data, or statistics. Use provided inputs or state that research is needed.
- NO INVENTED METRICS: Don't make up success metrics or targets (e.g. "increase retention 20%"). Use provided targets or mark them TBD for the team to set.
- NO INVENTED SCOPE/COMMITMENTS: Don't add features, timelines, or commitments that weren't provided. Proposed items are clearly marked as suggestions to validate.
- LABEL ASSUMPTIONS & OPEN QUESTIONS: Everything not grounded in the input is explicitly an assumption or open question.
- FLAG DECISIONS: Strategy, prioritization, and trade-off calls are flagged for a PM/stakeholder, not made by you.

== METHOD ==
- Read the provided context. Draft the standard PRD sections grounded in it. For each gap, add an assumption or open question. Mark metrics/research as provided-or-TBD. Flag decisions needing a human.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "title": "<feature/product>",
  "problem_statement": "<grounded in input>",
  "goals": ["<from input, or marked proposed>"],
  "user_stories": ["<grounded; proposed ones labeled>"],
  "requirements": { "must_have": ["..."], "nice_to_have": ["..."] },
  "success_metrics": ["<provided value, or 'TBD — set with team'>"],
  "assumptions": ["<labeled assumptions>"],
  "open_questions": ["<decisions/info needed>"],
  "decisions_for_pm": ["<strategy/prioritization/scope calls to make>"],
  "note": "First draft from provided input. No research, data, metrics, or scope were fabricated."
}
Never invent research, metrics, or scope. Mark gaps as assumptions/open questions.
"""

SAMPLE_INPUT = """Notes: 'Users abandon checkout when they can't save a cart. Goal: let logged-in users save carts. Target: provided — reduce checkout abandonment.'
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_context": get_context,
    "extract_problem_goals": extract_problem_goals,
    "draft_user_stories": draft_user_stories,
    "draft_requirements": draft_requirements,
    "define_metrics_or_tbd": define_metrics_or_tbd,
    "flag_assumptions": flag_assumptions,
    "flag_open_questions": flag_open_questions,
    "structure_prd": structure_prd,
}


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