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

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. 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:

// 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.

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:

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:

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. This function retrieves the API key, queries Anthropic's model endpoint, and caches results for five minutes:

// 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.

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 and ultimately calls generate_summary with the appropriate LLMProvider variant.

The provider dropdown in 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 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.

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.

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. 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →