import { CodeBlock } from '../views/docs/CodeBlock';
import { Callout, NextLink, Prose, PropTable } from '../views/docs/prose';
export const GUAVADIALER_EX = `import os
import guava
from guava import Agent, Field
from guava.campaigns import get_or_create_campaign, Contact

# 1. Create or retrieve a campaign
campaign = get_or_create_campaign(
    "political-poll-q2-2026",
    origin_phone_numbers=[os.environ["GUAVA_AGENT_NUMBER"]],
    calling_windows=[
        {"day": day, "start_time": "09:00", "end_time": "17:00"}
        for day in ["monday", "tuesday", "wednesday", "thursday", "friday"]
    ],
    start_date="2026-04-01",
    max_concurrency=3,
    max_attempts=2,
)

# 2. Upload contacts
campaign.upload_contacts(
    [
        Contact(phone_number="+15551234567", data={"first_name": "Alice", "district": "District 5"}),
        Contact(phone_number="+15559876543", data={"first_name": "Bob", "district": "District 12"}),
    ],
    accepted_terms_of_service=True,
)

# 3. Define call logic
agent = Agent(
    name="Jordan",
    organization="National Opinion Research Center",
    purpose="Conduct a non-partisan political opinion poll.",
)

@agent.on_call_start
def on_call_start(call: guava.Call):
    first_name = call.get_variable("first_name")
    call.reach_person(
        contact_full_name=first_name,
        greeting=(
            f"Hi, is this {first_name}? I'm calling from the National Opinion Research Center "
            "about issues affecting your district. Would you have two minutes to participate?"
        ),
    )

@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str):
    if outcome == "unavailable":
        call.hangup()
    elif outcome == "available":
        first_name = call.get_variable("first_name")
        district = call.get_variable("district")
        call.set_task(
            "political_poll",
            objective=(
                f"Conduct a brief political opinion poll with {first_name} "
                f"in {district}. Be polite and non-partisan."
            ),
            checklist=[
                Field(
                    key="willing_to_participate",
                    description="Whether the respondent agrees to take the poll",
                    field_type="multiple_choice",
                    choices=["yes", "no"],
                ),
                "If they said no, thank them and end the call.",
                Field(
                    key="top_issue",
                    description="Most important issue to the respondent",
                    question=f"What is the most important issue facing {district}?",
                    field_type="multiple_choice",
                    choices=["economy", "healthcare", "education", "housing",
                             "public_safety", "environment", "other"],
                ),
                Field(
                    key="likely_to_vote",
                    description="How likely they are to vote",
                    question="How likely are you to vote in the upcoming election?",
                    field_type="multiple_choice",
                    choices=["very_likely", "likely", "unlikely", "very_unlikely"],
                ),
            ],
        )

@agent.on_task_complete("political_poll")
def on_poll_complete(call: guava.Call):
    call.hangup("Thank them for participating.")

# 4. Serve the campaign (blocks until campaign completes or process is stopped)
agent.attach_campaign(campaign=campaign)`;

export const VOICEMAIL_DETECTION_EX = `import os
import logging

import guava
from guava import Agent, Field, Say
from guava.campaigns import create_or_update_campaign, Contact

logging.basicConfig(level=logging.INFO)

agent = Agent(
    name="Sarah",
    organization="Valley Health",
    purpose="Remind patients about upcoming appointments",
)


@agent.on_call_start
def on_call_start(call: guava.Call):
    patient_name = call.get_variable("patient_name")
    call.reach_person(
        contact_full_name=patient_name,
        greeting=(
            f"Hello, this is Sarah calling from Valley Health. "
            f"May I please speak with {patient_name}?"
        ),
    )


@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str):
    patient_name = call.get_variable("patient_name")
    appointment_date = call.get_variable("appointment_date")
    appointment_time = call.get_variable("appointment_time")
    office_number = call.get_variable("office_number")

    if outcome == "unavailable":
        call.read_script(
            f"Hello, this is a message for {patient_name}. "
            f"This is Sarah calling from Valley Health. "
            f"This is a friendly reminder about your appointment on "
            f"{appointment_date} at {appointment_time}. "
            f"Please call us at {office_number} if you need to make any changes. "
            f"Thank you!"
        )
        call.hangup()
    elif outcome == "available":
        call.set_task(
            "confirm_appointment",
            objective=f"Confirm the appointment with {patient_name}.",
            checklist=[
                Say(
                    f"Great! I'm calling to remind you about your appointment on "
                    f"{appointment_date} at {appointment_time}."
                ),
                Field(
                    key="appointment_response",
                    description="Whether the patient confirms, reschedules, or cancels",
                    field_type="multiple_choice",
                    choices=["confirmed", "reschedule", "cancel"],
                ),
            ],
        )


@agent.on_task_complete("confirm_appointment")
def on_appointment_confirmed(call: guava.Call):
    response = call.get_field("appointment_response")
    logging.info("Appointment response: %s", response)
    call.hangup("Thank them and end the call.")


if __name__ == "__main__":
    campaign = create_or_update_campaign(
        "appointment-reminders",
        origin_phone_numbers=[os.environ["GUAVA_AGENT_NUMBER"]],
        calling_windows=[
            {"day": day, "start_time": "09:00", "end_time": "17:00"}
            for day in ["monday", "tuesday", "wednesday", "thursday", "friday"]
        ],
        start_date="2026-04-01",
        max_concurrency=3,
        max_attempts=2,
    )

    campaign.upload_contacts(
        [
            Contact(
                phone_number="+15551234567",
                data={
                    "patient_name": "Alice Johnson",
                    "appointment_date": "April 22nd",
                    "appointment_time": "2:30 PM",
                    "office_number": "(555) 123-4567",
                },
            ),
        ],
        accepted_terms_of_service=True,
    )

    agent.attach_campaign(campaign=campaign)`;

## Campaign

Guavadialer is the SDK for creating and running high-volume outbound calling campaigns programmatically. Define campaigns, upload contacts, write per-call logic via `Agent` callbacks, and serve campaigns — all from Python.
Note that you need outbound permission approval to use outbound dialing; please contact support@goguava.ai.

### Campaign lifecycle

A campaign is created with `create_or_update_campaign()`, which upserts by name and returns an `Campaign` object. From there you upload contacts, serve the campaign, and monitor status.

### Campaign configuration

<PropTable rows={[
  { name: "campaign_name", type: "str", desc: "Unique campaign name per organization." },
  { name: "origin_phone_numbers", type: "list[str]", desc: "E.164 numbers owned by you. Calls are round-robin distributed across them." },
  { name: "calling_windows", type: "list[dict]", desc: 'Each dict: day (0–6 or name like "monday"), start_time ("HH:MM"), end_time ("HH:MM"). Times are in campaign timezone.' },
  { name: "start_date", type: "str", desc: '"YYYY-MM-DD" — when the campaign begins.' },
  { name: "end_date", type: "str | None", default: "None", desc: '"YYYY-MM-DD" — optional end date.' },
  { name: "max_concurrency", type: "int", default: "1", desc: "Maximum simultaneous active calls." },
  { name: "max_attempts", type: "int", default: "1", desc: "Maximum call attempts per contact before marking failed." },
  { name: "timezone", type: "str", default: '"America/Los_Angeles"', desc: "IANA timezone for calling windows." },
  { name: "description", type: "str", default: '""', desc: "Campaign description. Required when using Agentic Tenacity." },
]} />

### SDK methods

<PropTable rows={[
  { name: "create_or_update_campaign()", type: "function", desc: "Upserts a campaign by name. Returns an Campaign object. If the campaign already existed, updates with any provided fields" },
  { name: "list_campaigns()", type: "function", desc: "Returns all campaigns for the authenticated user." },
  { name: "campaign.upload_contacts()", type: "method", desc: 'Uploads contacts. Each contact is a Contact(phone_number="+1...", data={...}) object. accepted_terms_of_service must be True. Pass outreach_modalities to enable multi-channel outreach (see Agentic Tenacity). allow_duplicates is also available.' },
  { name: "campaign.get_status()", type: "method", desc: "Returns contact status histogram (trying, completed, partially_completed, failed)." },
  { name: "campaign.update()", type: "method", desc: "Updates mutable campaign fields (concurrency, calling windows, etc.)." },
  { name: "campaign.delete()", type: "method", desc: "Soft-deletes the campaign." },
]} />

### Contact statuses

<div className="my-4 overflow-x-auto rounded-lg border border-border">
  <table className="w-full text-sm font-mono">
    <thead>
      <tr className="border-b border-border bg-card/50">
        <th className="text-left px-4 py-3 text-muted-foreground font-semibold">Status</th>
        <th className="text-left px-4 py-3 text-muted-foreground font-semibold">Meaning</th>
      </tr>
    </thead>
    <tbody>
      <tr className="bg-transparent">
        <td className="px-4 py-3 text-primary">trying</td>
        <td className="px-4 py-3 text-muted-foreground">Eligible for dispatch</td>
      </tr>
      <tr className="bg-card/20">
        <td className="px-4 py-3 text-primary">completed</td>
        <td className="px-4 py-3 text-muted-foreground">Call finished successfully</td>
      </tr>
      <tr className="bg-transparent">
        <td className="px-4 py-3 text-primary">partially_completed</td>
        <td className="px-4 py-3 text-muted-foreground">Call connected but not all tasks done</td>
      </tr>
      <tr className="bg-card/20">
        <td className="px-4 py-3 text-primary">failed</td>
        <td className="px-4 py-3 text-muted-foreground">Permanent error or max attempts exhausted</td>
      </tr>
      <tr className="bg-transparent">
        <td className="px-4 py-3 text-primary">do_not_call</td>
        <td className="px-4 py-3 text-muted-foreground">Contact has asked not to be called</td>
      </tr>
    </tbody>
  </table>
</div>

### Agent callbacks

Define an `Agent` and attach callbacks using decorators. Per-call data from the contact's `data` dict is available via `call.get_variable()`. Use `on_call_start` to initiate contact, `on_reach_person` to branch on availability, and `on_task_complete` to wrap up.

### Serving a campaign

Use `agent.attach_campaign(campaign=campaign)` to start processing calls. This blocks and handles calls as they are dispatched by the autodialer. Multiple processes can run in parallel for horizontal scaling.

The autodialer dispatches calls every 2 minutes within configured calling windows. Calls that fail with retryable SIP errors are automatically rescheduled up to `max_attempts`.

`attach_campaign` polls `v1/campaigns/{id}/has-callable-contacts` every 5 seconds. When no callable contacts remain and no active calls exist, the socket closes cleanly and `attach_campaign` returns with a log: `INFO: Campaign '<name>' has no more callable contacts and no active calls. Closing.` (Added in v0.21; previously `attach_campaign` blocked indefinitely until the process was killed.)

### Complete example

<CodeBlock code={GUAVADIALER_EX} filename="campaign.py" language="python" />

<Callout><span className="text-primary font-semibold">Auth:</span> All SDK calls require the `GUAVA_API_KEY` env var set to a valid API key. The base URL defaults to production; override with `GUAVA_BASE_URL`.</Callout>

### Voicemail detection and handling

Not every outbound call reaches a live person, and that's fine. Rather than using `reach_person()` (which simply succeeds or fails), you can build custom routing logic to handle voicemail, wrong numbers, and do-not-contact requests, turning every call outcome into a useful action.

The pattern: use a `Field` with `field_type="multiple_choice"` to classify who answered — the target contact, someone else, voicemail, or a wrong number. Then route to the appropriate handler based on the result.

<Prose><span className="text-foreground">read_script()</span> — delivers a scripted message verbatim (no LLM improvisation). Ideal for voicemail messages where you need precise, compliant wording.</Prose>

<Prose><span className="text-foreground">Multi-phase tasks.</span> Call `set_task()` again from `on_complete` to chain tasks. The first task identifies who answered; the second task handles the actual conversation or voicemail.</Prose>

<Prose><span className="text-foreground">Graceful exits.</span> Handle every outcome — confirmed identity, unavailable, wrong number, do-not-contact — so no call ends awkwardly or without logging.</Prose>

<CodeBlock code={VOICEMAIL_DETECTION_EX} filename="voicemail_detection.py" language="python" />

<NextLink section="agentic-tenacity" label="Agentic Tenacity" />
