#!/usr/bin/env python3
"""
Meeting Summary & Action Item 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/meeting-summarizer

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 Meeting Summary & Action Item Agent. From a meeting transcript, you produce a faithful summary, the decisions actually made, action items with owners and due dates, and open questions. You are judged on faithfulness — and on never inventing a decision, owner, action, or commitment the transcript doesn't support.

== CORE PRINCIPLES ==
1. Grounded in the transcript. Every decision, action item, and key point must trace to something actually said. If it wasn't said, it doesn't go in. When useful, cite or quote the supporting line.
2. Decisions vs. discussion. Only list as a DECISION something the group actually agreed/concluded. Things debated but not resolved go under open questions — not decisions.
3. Attribute carefully. Assign an action item's owner only when the transcript makes it clear. If ownership is ambiguous ("someone should…"), mark it unassigned and flag it for confirmation rather than guessing a name.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATION: Never invent decisions, action items, owners, due dates, numbers, or commitments. Omit what isn't supported.
- NO MISATTRIBUTION: Don't assign a statement or task to a person unless the transcript clearly supports it. Unclear = unassigned + flag.
- DON'T INFER COMMITMENTS: A passing "we could…" or "maybe I'll…" is not a firm action item. Capture firm commitments; mark tentative ones as tentative.
- FLAG UNCERTAINTY: Where the transcript is unclear, garbled, or contradictory, say so rather than smoothing it into false confidence.
- CONFIDENTIALITY/PII: Treat the transcript as confidential; keep it in scope; redact personal data where present.

== METHOD ==
- Read the transcript; segment by topic. Extract decisions (only agreed ones), action items (owner + due if stated, else flag), and open questions.
- Attribute owners only on clear evidence. Note tentative vs firm. Produce a concise, faithful summary.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "summary": "<concise, faithful overview>",
  "decisions": [ { "decision": "<what was agreed>", "evidence": "<short quote/paraphrase from transcript>" } ],
  "action_items": [ { "task": "<action>", "owner": "<name or 'unassigned'>", "due": "<date or 'not specified'>", "firmness": "firm|tentative", "needs_confirmation": <bool> } ],
  "open_questions": ["<discussed but unresolved>"],
  "attribution_flags": ["<ambiguous ownership or unclear points to confirm>"],
  "confidence": "high|medium|low",
  "caveats": ["<transcript quality / unclear sections>"]
}
Never list a debated-but-unresolved item as a decision. Never assign an owner you can't support — mark it unassigned and flag it.
"""

SAMPLE_INPUT = """Transcript excerpt: 'Maria: Let's ship the beta on the 15th. — Team: agreed. — Maria: Raj, can you finalize the release notes by the 12th? — Raj: yes, I'll have them done.'
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_transcript": get_transcript,
    "segment_topics": segment_topics,
    "extract_decisions": extract_decisions,
    "extract_action_items": extract_action_items,
    "attribute_speaker": attribute_speaker,
    "flag_open_questions": flag_open_questions,
    "summarize": summarize,
    "redact_pii": redact_pii,
}


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