# Hikigai AppSDK - LLM Context

## Overview

`hikigai-appsdk` - Python SDK for invoking AI agents in your applications, with advanced capabilities like MCP connectors, plugins (e.g. SONA personalization), and healthcare clinical confidence/safety tracking.

## Installation

```bash
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ hikigai-appsdk
```

## Quick Start

```python
from hikigai.appsdk import AppClient

client = AppClient(
    api_key="your-api-key",
    project_id="your-project-id",
    # Optional SONA config for personalization tracking/suggestions
    sona_url="http://localhost:8002",
    sona_api_key="sona-api-key"
)

# Get an agent
agent = client.agent("medical-coder")

# 1. Basic Invocation
response = agent.invoke("Patient presents with fever and cough...")
print(response.content)

# 2. Invocation with MCP connectors and Plugins (SONA)
response = agent.invoke(
    input="Patient presents with fever and cough...",
    connectors={"epic-ehr": {"EPIC_CLIENT_ID": "xxx"}},
    plugin_context={"sona": {"user_id": "dr-smith-uuid"}}
)

if response.status == "success":
    print("Agent Content:", response.content)
    
    # Optional structured output
    if response.output:
        print("Structured Data:", response.output)
    
    # Healthcare Clinical Confidence and Safety Flags
    if response.confidence:
        print("Confidence:", response.confidence.score)
    for flag in response.safety_flags:
        print("Safety Warning:", flag.message)
        
    # SONA Personalization plugins logic
    if response.plugins and "sona" in response.plugins:
        sona_data = response.plugins["sona"]
        print("SONA Note ID:", sona_data.get("output_id"))

# 3. Stream responses
for chunk in agent.stream("Tell me a story"):
    print(chunk, end="")

# 4. Session-based conversations
session_agent = agent.with_session("user-123")
session_agent.invoke("What is diabetes?")
```

## Key APIs

### AppClient

Main client for agent invocation.

#### Initialization args
- `api_key: Optional[str]` - API key (can be sourced from `HIKIGAI_API_KEY`)
- `project_id: Optional[str]` - Project ID (can be sourced from `HIKIGAI_PROJECT_ID`)
- `base_url: Optional[str]` - Base URL for SDK requests
- `sona_url: Optional[str]` - Base URL for SONA client requests
- `sona_api_key: Optional[str]` - Headers API Key for SONA client requests

#### Methods / Properties
- `agent(agent_id: str) -> RuntimeAgent` - Get agent by slug or ID
- `list_agents() -> List[RuntimeAgent]` - List all available agents
- `sona -> SONAClient` - Allows accessing personalization features securely.

### RuntimeAgent

Represents a deployed agent ready for invocation.

#### Methods
- `invoke(input, session_id, provider, model, timeout, connectors, plugin_context) -> InvokeResponse` - Synchronous call. Returns a structured output container.
- `stream(...) -> Iterator[str]` - Stream agent textual chunks
- `with_session(session_id: str) -> RuntimeAgent` - Create session-based context

### InvokeResponse

Rich structured response from agent invocation including healthcare metadata.

#### Fields
- `content` (str) - Response content
- `status` (str) - success, error, etc.
- `output` (Dict, optional) - Rich dictionary response
- `confidence` (ClinicalConfidence, optional) - Contains `.score`, `.quality_metrics`, `.reasoning`
- `safety_flags` (List[SafetyFlag]) - Each flag has a `.severity` and `.message`
- `citations` (List[ClinicalCitation])
- `metadata` (InvocationMetadata) - Latency, token usage, tool calls, phi-redaction flag, trace ID.
- `plugins` (Dict, optional) - SONA output_id, etc.

### SONAClient (Accessible via client.sona)

Manages note-writing patterns securely per-user.

#### Methods
- `submit_edit(output_id, final_text, user_id)`
- `approve_output(output_id, user_id)`
- `get_preferences(user_id)`
- `update_preferences(user_id, agent_id, **kwargs)`
- `get_suggestions(user_id)`
- `respond_to_suggestion(pattern_id, response)`

### CloudClient (Accessible via client.cloud)

Multi-cloud deployment catalog and BYOC credentials. Everything is
catalog-driven — a target added on the backend is selectable with no SDK
release.

#### Catalog
- `catalog(workload=None)` -> `CloudCatalog` (providers, services, regions, zones, JSON Schemas)
- `services(provider=None, workload=None)` / `service(service_id)` / `providers()`
- `CloudService.defaults()` -> config defaults read from the schema
- `CloudService.required_fields()` -> required fields with no default
- Built-in services: gcp-cloud-run, gcp-agent-engine, gcp-gke-autopilot,
  aws-ecs-fargate, aws-app-runner, aws-bedrock-agentcore, aws-eks

#### Dry-run a target before deploying
```python
result = client.cloud.validate_target(
    workload="app", service_id="gcp-cloud-run",
    region="europe-west1", config={"cpu": "2"},
)
if not result.valid:
    raise SystemExit(result.error)      # returns valid=False, does not raise
```

#### Credentials (write-only — no method returns stored material)
- `create_credential(name=..., kind=..., payload={...})` — kinds:
  aws_access_key | aws_assume_role | gcp_service_account
- `list_credentials()` -> masked hints only
- `rotate_credential(id, payload)` — keeps the id, so deployments still resolve
- `validate_credential(id)` / `update_credential(id, ...)` / `delete_credential(id)`
- With no credential attached, deployments use the platform's cloud account

#### Register a target at runtime
- `register_service(service_id=..., provider=..., display_name=..., workloads=[...], config_schema={...})`
- Registering an existing id overrides the built-in


### EventsClient (Accessible via client.events)

Platform event bus: tenant webhooks and a live event stream. Applications
react to `job.*`, `agent.deployed`, `agent.deleted`, `invocation.completed`,
`storage.*`, `stream.*` instead of polling.

#### Webhook management
- `create_webhook(url=..., event_types=[...], description=None)` -> `WebhookSubscription` (`.secret` shown ONCE)
- `list_webhooks()` / `get_webhook(id)` / `delete_webhook(id)`
- `update_webhook(id, url=..., event_types=..., description=..., is_active=...)` - omitted fields unchanged
- `rotate_secret(id)` -> new secret (old one stops verifying immediately)
- `test_webhook(id)` -> `{"delivered": bool, "delivery": {...}}` synthetic `webhook.test` ping
- `list_deliveries(id, status=None, limit=50)` -> `[WebhookDelivery]` (`status`: pending | success | failed | dead)
- `redeliver(id, delivery_id)` - retry a failed/dead delivery now

#### Signature verification (pure functions - no client, no network)
```python
from hikigai.appsdk import parse_webhook_event, SignatureVerificationError

event = parse_webhook_event(
    secret=os.environ["HIKIGAI_WEBHOOK_SECRET"],
    signature_header=request.headers["X-Hikigai-Signature"],
    body=request.get_data(as_text=True),   # RAW body — re-serialized JSON will NOT match
)
```
- `verify_webhook_signature(secret, signature_header, body, tolerance_seconds=300)` -> bool
- `parse_webhook_event(...)` -> envelope dict, raises `SignatureVerificationError`
- Header: `X-Hikigai-Signature: t=<unix-ts>,v1=<hex hmac_sha256(secret, "<t>.<raw-body>")>`
- Delivery is AT-LEAST-ONCE — dedupe on `event["id"]`

#### Live stream (requires `hikigai-appsdk[live]`)
```python
async with client.events.stream(patterns=["job.*"]) as stream:
    async for event in stream:
        print(event["type"], event["data"])
```
At-most-once, no replay cursor; `reconnect()` resumes from now.


## Error Handling

Core exceptions: `HikigaiError`, `AuthenticationError`, `RateLimitError`, `AgentNotFoundError`, `InvocationError`, `ConfigurationError`.

```python
try:
    response = agent.invoke("input")
except AuthenticationError:
    print("Invalid API key")
except InvocationError as e:
    print(f"Failed to run: {e}")
```

## Documentation

Full docs: https://docs.hikigai.com/appsdk
