# How Meetily Integrates Cloud AI Providers Like Claude and OpenAI: A Technical Deep Dive

> Discover how Meetily integrates Claude and OpenAI with its Rust client. Automatically handle endpoints, authentication, and parsing via a single generate_summary function.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: deep-dive
- Published: 2026-07-11

---

**Meetily unifies Claude, OpenAI, and other cloud AI services through a provider-agnostic Rust client that automatically handles endpoint selection, authentication headers, and response parsing via a single `generate_summary` function.**

Meetily is an open-source meeting assistant that leverages multiple large language models (LLMs) for generating meeting summaries. The application integrates cloud AI providers through a centralized architecture in its Rust-based Tauri backend, enabling seamless switching between Claude, OpenAI, Groq, and other services without modifying the core summarization logic.

## Architecture Overview

Meetily's integration strategy centers on abstraction. Instead of implementing separate clients for each AI service, the codebase defines a unified interface in [`frontend/src-tauri/src/summary/llm_client.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/summary/llm_client.rs). This approach allows the frontend to pass a provider identifier, model name, and API credentials to a single entry point, while the backend handles provider-specific peculiarities internally.

The system supports **OpenAI**, **Anthropic (Claude)**, **Groq**, **OpenRouter**, **Ollama**, **Built-in AI**, and **Custom OpenAI-compatible endpoints**. Each provider implements the same contract but uses distinct base URLs, header configurations, and request schemas.

## The Provider-Agnostic LLM Client

At the heart of the integration sits the `generate_summary` function, an asynchronous Rust handler that coordinates all external AI communications.

### The LLMProvider Enum

The client defines an `LLMProvider` enum covering all supported services. This enum drives conditional logic for endpoint selection and payload construction:

```rust
// From frontend/src-tauri/src/summary/llm_client.rs
pub enum LLMProvider {
    OpenAI,
    Claude,
    Groq,
    OpenRouter,
    Ollama,
    BuiltInAI,
    CustomOpenAI,
}

```

When the frontend invokes the summary generation via Tauri commands, it passes the selected variant along with the model configuration.

### Dynamic Endpoint and Header Configuration

The `generate_summary` function matches the provider enum to configure the HTTP request specifics. For **OpenAI**, it uses the standard `Authorization: Bearer` header against `https://api.openai.com/v1/chat/completions`. For **Claude**, it applies Anthropic's required `x-api-key` and `anthropic-version` headers to the `https://api.anthropic.com/v1/messages` endpoint.

```rust
let (api_url, mut headers) = match provider {
    LLMProvider::OpenAI => (
        "https://api.openai.com/v1/chat/completions".into(), 
        HeaderMap::new()
    ),
    LLMProvider::Claude => {
        let mut h = HeaderMap::new();
        h.insert("x-api-key", api_key.parse()?);
        h.insert("anthropic-version", "2023-06-01".parse()?);
        ("https://api.anthropic.com/v1/messages".into(), h)
    },
    // ... other providers
};

```

## Request Building and Payload Normalization

Meetily handles structural differences between AI providers through conditional serialization.

### OpenAI-Compatible Request Format

For OpenAI and similar services, the client constructs a `ChatRequest` struct containing the model identifier, message list, and optional parameters like temperature and max tokens.

### Claude-Specific Request Structure

Anthropic's API requires a distinct payload format. The client builds a `ClaudeRequest` struct that includes a top-level `system` prompt field and explicit `max_tokens` configuration:

```rust
let request_body = if provider == &LLMProvider::Claude {
    serde_json::json!(ClaudeRequest {
        model: model_name.to_string(),
        max_tokens: 1024,
        system: system_prompt.to_string(),
        messages: vec![ChatMessage { 
            role: "user".into(), 
            content: user_prompt.into() 
        }],
    })
} else {
    serde_json::json!(ChatRequest {
        model: model_name.to_string(),
        messages: vec![ChatMessage { 
            role: "user".into(), 
            content: user_prompt.into() 
        }],
        max_tokens: None,
        temperature: None,
        top_p: None,
    })
};

```

The function uses a shared `reqwest::Client` instance for connection pooling and performance optimization.

## Response Parsing and Normalization

After sending the asynchronous HTTP POST, the client normalizes divergent response schemas into a unified string output.

For **OpenAI**, the response deserializes into `ChatResponse`, extracting content from `choices[0].message.content`. For **Claude**, the client parses `ClaudeChatResponse`, which contains a `content` array of `ClaudeChatContent` items, concatenating the text fields:

```rust
let text = if provider == &LLMProvider::Claude {
    let cl: ClaudeChatResponse = resp.json().await.map_err(|e| e.to_string())?;
    cl.content.into_iter().map(|c| c.text).collect::<String>()
} else {
    let oc: ChatResponse = resp.json().await.map_err(|e| e.to_string())?;
    oc.choices.into_iter().next().unwrap().message.content
};

```

This normalization ensures the frontend receives a consistent `Result<String, String>` regardless of the backend provider.

## Frontend Integration and Model Management

The TypeScript frontend manages provider selection and model discovery through React components and Tauri command invocations.

### Loading Available Claude Models

When users select Claude as their provider, the UI fetches available models via the `get_anthropic_models` command defined in [`frontend/src-tauri/src/anthropic/anthropic.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/anthropic/anthropic.rs). This function retrieves the API key, queries Anthropic's model endpoint, and caches results for five minutes:

```typescript
// From frontend/src/components/ModelSettingsModal.tsx
const loadClaudeModels = async (key: string | null) => {
  setIsLoadingClaude(true);
  try {
    const models = await invoke<string[]>('get_anthropic_models', { apiKey: key });
    setClaudeModels(models);
  } catch (err) {
    console.error('Error loading Claude models:', err);
    setClaudeModels(CLAUDE_FALLBACK_MODELS);
  } finally {
    setIsLoadingClaude(false);
  }
};

```

The command returns chat-capable models like `claude-sonnet-...` and `claude-haiku-...`, which populate the dropdown in [`ModelSettingsModal.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/ModelSettingsModal.tsx).

### State Management and Provider Selection

The UI stores the selected configuration in a `modelConfig` state object. When a meeting begins, the frontend passes this configuration to the Rust backend via `api_get_model_config`, which triggers the summary orchestration in [`frontend/src-tauri/src/summary/processor.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/summary/processor.rs) and ultimately calls `generate_summary` with the appropriate `LLMProvider` variant.

The provider dropdown in [`frontend/src/_components/SettingsModal.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/_components/SettingsModal.tsx) renders options like `<SelectItem value="claude">Claude</SelectItem>`, mapping user selections to the correct enum variants.

## Summary

- Meetily implements a **provider-agnostic architecture** in [`frontend/src-tauri/src/summary/llm_client.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/summary/llm_client.rs) that unifies Claude, OpenAI, and other cloud AI services behind a single interface.
- The **`generate_summary` function** automatically selects endpoints, configures authentication headers, and normalizes request payloads based on the `LLMProvider` enum variant.
- **Claude integration** requires special handling for the `x-api-key` header, `anthropic-version` metadata, and the `system` prompt field in request bodies.
- The frontend fetches dynamic model lists for Claude via **`get_anthropic_models`** while caching results to minimize API calls.
- Adding new providers requires only extending the `LLMProvider` enum and implementing the endpoint/header logic, without touching the summarization orchestration in [`processor.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/processor.rs).

## Frequently Asked Questions

### How does Meetily handle different authentication schemes for Claude versus OpenAI?

Meetily's `generate_summary` function uses pattern matching on the `LLMProvider` enum to configure provider-specific headers. OpenAI uses the standard `Authorization: Bearer` header, while Claude requires the `x-api-key` and `anthropic-version` headers. This logic is encapsulated in the header construction phase of the HTTP client setup in [`frontend/src-tauri/src/summary/llm_client.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/summary/llm_client.rs).

### Can I add a custom OpenAI-compatible endpoint to Meetily?

Yes. The `LLMProvider` enum includes a `CustomOpenAI` variant that allows users to specify custom base URLs. The system treats these endpoints as OpenAI-compatible, using the standard `ChatRequest` struct and `Authorization: Bearer` authentication scheme, making it compatible with local inference servers or third-party OpenAI proxies.

### Where does the model list for Claude come from?

The frontend calls the `get_anthropic_models` Tauri command defined in [`frontend/src-tauri/src/anthropic/anthropic.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/anthropic/anthropic.rs). This function queries the Anthropic API for available models, filters for chat-capable variants, and caches the results for five minutes. If the API call fails, the UI falls back to a hardcoded list defined as `CLAUDE_FALLBACK_MODELS` in the TypeScript code.

### Is the HTTP client reused across requests for performance?

Yes. The implementation uses a shared `reqwest::Client` instance that is passed to `generate_summary` and reused across multiple API calls. This enables connection pooling and DNS caching, significantly improving performance when generating multiple meeting summaries or fetching model lists repeatedly.