# API Reference

Quick reference for using the porta library.

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
porta = { path = "../porta" }
tokio = { version = "1", features = ["full"] }
```

## Quick Start

```rust
use provider_connections::anthropic::{
    AnthropicOAuth, AnthropicClient, AuthMode, OAuthTokens
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Create OAuth handler
    let oauth = AnthropicOAuth::new();

    // 2. Get authorization URL
    let (url, verifier) = oauth.authorize_url(AuthMode::Max)?;
    println!("Visit: {}", url);

    // 3. Exchange code for tokens (after user authorizes)
    let code = "paste_code_here";
    let tokens = oauth.exchange(code, &verifier).await?;

    // 4. Create client
    let client = AnthropicClient::new(tokens);

    // 5. Send messages
    let response = client.messages(r#"{
        "model": "claude-sonnet-4-20250514",
        "max_tokens": 1024,
        "messages": [{"role": "user", "content": "Hello!"}]
    }"#).await?;

    println!("{}", response);
    Ok(())
}
```

---

## Modules

### `provider_connections::error`

```rust
use provider_connections::{Error, Result};
```

#### `Error` (enum)

| Variant | Description |
|---------|-------------|
| `Error::Http(reqwest::Error)` | HTTP request failed |
| `Error::Json(serde_json::Error)` | JSON parsing failed |
| `Error::OAuth(String)` | OAuth-specific error |
| `Error::TokenRefresh { status: u16 }` | Token refresh failed |
| `Error::TokenExpired` | Token expired, no refresh available |
| `Error::Url(url::ParseError)` | URL parsing failed |

#### `Result<T>` (type alias)

```rust
pub type Result<T> = std::result::Result<T, Error>;
```

---

### `provider_connections::pkce`

```rust
use provider_connections::pkce::Pkce;
```

#### `Pkce` (struct)

PKCE code verifier and challenge pair.

| Field | Type | Description |
|-------|------|-------------|
| `verifier` | `String` | Random secret (43 chars, base64url) |
| `challenge` | `String` | SHA-256 of verifier (43 chars, base64url) |

**Methods:**

```rust
impl Pkce {
    /// Generate a new PKCE pair
    pub fn generate() -> Self;

    /// Get the challenge method (always "S256")
    pub fn method() -> &'static str;
}
```

**Example:**

```rust
let pkce = Pkce::generate();
println!("Verifier: {}", pkce.verifier);
println!("Challenge: {}", pkce.challenge);
println!("Method: {}", Pkce::method());  // "S256"
```

---

### `provider_connections::anthropic`

```rust
use provider_connections::anthropic::{
    AnthropicOAuth,
    AnthropicClient,
    AuthMode,
    OAuthTokens,
    RequestTransformer,
    // Constants
    CLIENT_ID,
    USER_AGENT,
    TOOL_PREFIX,
    REQUIRED_BETAS,
};
```

#### `AuthMode` (enum)

```rust
pub enum AuthMode {
    Max,      // Claude Pro/Max subscription (claude.ai)
    Console,  // API key creation (console.anthropic.com)
}
```

#### `OAuthTokens` (struct)

```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthTokens {
    pub access_token: String,
    pub refresh_token: String,
    pub expires_at: u64,  // Unix timestamp in milliseconds
}
```

**Methods:**

```rust
impl OAuthTokens {
    /// Check if the access token is expired
    pub fn is_expired(&self) -> bool;
}
```

#### `AnthropicOAuth` (struct)

Handles the OAuth flow.

```rust
impl AnthropicOAuth {
    /// Create a new OAuth handler
    pub fn new() -> Self;

    /// Generate authorization URL and PKCE verifier
    /// Returns: (authorization_url, pkce_verifier)
    pub fn authorize_url(&self, mode: AuthMode) -> Result<(String, String)>;

    /// Exchange authorization code for tokens
    pub async fn exchange(&self, code: &str, verifier: &str) -> Result<OAuthTokens>;

    /// Refresh an expired access token
    pub async fn refresh(&self, refresh_token: &str) -> Result<OAuthTokens>;
}
```

**Example:**

```rust
let oauth = AnthropicOAuth::new();

// Get URL for Claude Pro/Max
let (url, verifier) = oauth.authorize_url(AuthMode::Max)?;

// Or for API key creation
let (url, verifier) = oauth.authorize_url(AuthMode::Console)?;

// After user authorizes and provides code
let tokens = oauth.exchange(&code, &verifier).await?;

// Later, refresh if expired
if tokens.is_expired() {
    let new_tokens = oauth.refresh(&tokens.refresh_token).await?;
}
```

#### `AnthropicClient` (struct)

High-level API client with automatic token refresh.

```rust
impl AnthropicClient {
    /// Create a new client with tokens
    pub fn new(tokens: OAuthTokens) -> Self;

    /// Send a chat completion request
    /// Handles: token refresh, headers, body transformation
    pub async fn messages(&self, body: &str) -> Result<String>;

    /// Send a streaming chat completion request
    pub async fn messages_stream(&self, body: &str)
        -> Result<Pin<Box<dyn Stream<Item = Result<Bytes>> + Send>>>;

    /// Get current tokens (for persistence)
    pub async fn tokens(&self) -> OAuthTokens;

    /// Update tokens
    pub async fn set_tokens(&self, tokens: OAuthTokens);
}
```

**Example:**

```rust
let client = AnthropicClient::new(tokens);

// Simple request
let response = client.messages(r#"{
    "model": "claude-sonnet-4-20250514",
    "max_tokens": 1024,
    "messages": [
        {"role": "user", "content": "What is 2+2?"}
    ]
}"#).await?;

// Save tokens after use (may have been refreshed)
let current_tokens = client.tokens().await;
save_to_file(&current_tokens)?;
```

#### `RequestTransformer` (struct)

Transforms requests and responses for OAuth compatibility.

```rust
impl RequestTransformer {
    /// Transform outgoing request body
    /// - Injects Claude Code system prompt
    /// - Adds empty tools array if missing
    /// - Prefixes tool names with "mcp_"
    /// - Sanitizes "OpenCode" -> "Claude Code"
    pub fn transform_request(body: &str) -> Option<String>;

    /// Transform incoming response
    /// - Removes "mcp_" prefix from tool names
    pub fn transform_response(chunk: &str) -> String;
}
```

**Example:**

```rust
let body = r#"{"messages":[{"role":"user","content":"Hello"}]}"#;
let transformed = RequestTransformer::transform_request(body).unwrap();
// Now includes system prompt and tools array

let response = r#"{"name":"mcp_read_file"}"#;
let cleaned = RequestTransformer::transform_response(response);
// Returns: {"name":"read_file"}
```

---

## Constants

```rust
use provider_connections::anthropic::*;

// OAuth client ID (Claude Code's)
CLIENT_ID: &str = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"

// OAuth redirect URI
REDIRECT_URI: &str = "https://console.anthropic.com/oauth/code/callback"

// OAuth scopes
SCOPES: &str = "org:create_api_key user:profile user:inference"

// User-Agent header (mimics Claude CLI)
USER_AGENT: &str = "claude-cli/2.1.2 (external, cli)"

// Tool name prefix
TOOL_PREFIX: &str = "mcp_"

// Required beta headers
REQUIRED_BETAS: &[&str] = &[
    "oauth-2025-04-20",
    "interleaved-thinking-2025-05-14",
]
```

---

## CLI Usage

```bash
# Build
cargo build --bin porta

# Run
cargo run --bin porta

# With release optimizations
cargo run --release --bin porta
```

### Token Storage

Tokens are stored at: `~/.porta/tokens.json`

```bash
# View tokens
cat ~/.porta/tokens.json

# Delete tokens (force re-auth)
rm ~/.porta/tokens.json
```

---

## Error Handling

```rust
use provider_connections::{Error, Result};

async fn example() -> Result<()> {
    let oauth = AnthropicOAuth::new();

    match oauth.exchange(code, verifier).await {
        Ok(tokens) => println!("Got tokens!"),
        Err(Error::OAuth(msg)) => println!("OAuth error: {}", msg),
        Err(Error::Http(e)) => println!("HTTP error: {}", e),
        Err(e) => println!("Other error: {}", e),
    }

    // Or use ? for propagation
    let tokens = oauth.exchange(code, verifier).await?;

    Ok(())
}
```

---

## Thread Safety

`AnthropicClient` is thread-safe and can be shared across async tasks:

```rust
use std::sync::Arc;

let client = Arc::new(AnthropicClient::new(tokens));

// Clone Arc for multiple tasks
let client1 = Arc::clone(&client);
let client2 = Arc::clone(&client);

tokio::spawn(async move {
    client1.messages("...").await
});

tokio::spawn(async move {
    client2.messages("...").await
});
```
