# How to Implement a Custom Provider for a Proprietary LLM API in Goose: A Complete Guide

> Learn how to implement a custom provider for a proprietary LLM API in Goose. This guide covers trait implementation, API client configuration, and registration for seamless integration.

- Repository: [Block Open Source/goose](https://github.com/block/goose)
- Tags: how-to-guide
- Published: 2026-04-05

---

**To integrate a private or proprietary LLM into Goose, you implement the `Provider` and `ProviderDef` traits, configure an `ApiClient` with your authentication method, and register the implementation via `ProviderRegistry::register` or a JSON declarative configuration.**

Goose is an open-source agent framework from Block that abstracts LLM interactions through a unified provider interface. If your organization runs a custom model behind a private API, you can make it appear as a first-class provider by following the architecture patterns defined in [`crates/goose/src/providers/base.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/base.rs).

## Understanding the Provider Architecture

Before writing code, you need to understand the three core abstractions that Goose uses to manage LLM integrations.

### The `Provider` Trait

The **`Provider`** trait in [`crates/goose/src/providers/base.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/base.rs) (lines 50-78) defines the runtime contract for streaming completions. Every provider must implement `stream()`, which returns a `MessageStream` of response chunks. The trait also includes optional methods for embeddings and OAuth configuration.

### The `ProviderDef` Trait

The **`ProviderDef`** trait in [`crates/goose/src/providers/base.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/base.rs) (lines 82-96) supplies static metadata and construction logic. It requires two methods:

1. `metadata()` – Returns a `ProviderMetadata` struct containing the display name, supported models, and configuration keys.
2. `from_env()` – A factory function that reads environment variables or config files and returns an initialized provider instance.

### The `ProviderRegistry`

The **`ProviderRegistry`** in [`crates/goose/src/providers/provider_registry.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/provider_registry.rs) (lines 77-84) maintains a map of provider names to constructor functions. You register your implementation using `register::<YourProvider>()` or `register_with_name()` for dynamic registration.

### Supporting Components

- **`ApiClient`** ([`crates/goose/src/providers/api_client.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/api_client.rs)): Handles HTTP requests, timeouts, header injection, and authentication methods (Bearer tokens, OAuth, etc.).
- **`ConfigKey`** ([`crates/goose/src/providers/base.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/base.rs) lines 58-76): Describes configuration fields exposed to users in the CLI or UI, including whether they are secret (stored in keychain) or required.
- **`DeclarativeProviderConfig`** ([`crates/goose/src/config/declarative_providers.rs`](https://github.com/block/goose/blob/main/crates/goose/src/config/declarative_providers.rs) lines 60-71): Enables zero-code provider registration via JSON files placed in `$XDG_CONFIG_HOME/goose/custom_providers/`.

## Step-by-Step Implementation

Follow these steps to build a complete provider for a proprietary API endpoint.

### Step 1: Create the Provider Module

Create a new file at [`crates/goose/src/providers/mycorp.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/mycorp.rs). This module will contain your struct and trait implementations.

```rust
use super::base::{
    Provider, ProviderDef, ProviderMetadata, ProviderUsage, MessageStream,
    stream_from_single_message, ConfigKey,
};
use super::api_client::{ApiClient, AuthMethod};
use crate::{model::ModelConfig, config::Config};
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use futures::future::BoxFuture;
use rmcp::model::Tool;

```

### Step 2: Define the Provider Struct

Your struct must hold an **`ApiClient`** instance and the **`ModelConfig`**:

```rust
pub struct MyCorpProvider {
    api_client: ApiClient,
    model: ModelConfig,
    name: String,
}

```

### Step 3: Implement `ProviderDef` for Static Metadata

Implement the metadata constructor to define how users configure your provider:

```rust
#[async_trait]
impl ProviderDef for MyCorpProvider {
    type Provider = Self;

    fn metadata() -> ProviderMetadata {
        ProviderMetadata::new(
            "mycorp",                         // Internal ID
            "MyCorp LLM",                     // Display name in UI
            "Proprietary LLM hosted at https://api.mycorp.com/v1",
            "mycorp-7b",                      // Default model
            vec!["mycorp-7b", "mycorp-13b"], // Supported models
            "https://docs.mycorp.com/models", // Documentation URL
            vec![
                ConfigKey::new("MYCORP_API_KEY", true, true, None, true),
                ConfigKey::new("MYCORP_CUSTOM_HEADER", false, false, None, false),
            ],
        )
        .with_setup_steps(vec![
            "Create an API key in the MyCorp console",
            "Run `goose configure` and paste the key when prompted",
        ])
    }

    fn from_env(
        model: ModelConfig,
        _extensions: Vec<crate::config::ExtensionConfig>,
    ) -> BoxFuture<'static, Result<Self::Provider>> {
        Box::pin(async move {
            let cfg = Config::global();
            let api_key = cfg.get_secret::<String>("MYCORP_API_KEY")
                .map_err(|_| anyhow!("Missing MYCORP_API_KEY"))?;
            
            let base_url = cfg.get_param::<String>("MYCORP_HOST")
                .unwrap_or_else(|_| "https://api.mycorp.com/v1".to_string());

            let mut client = ApiClient::with_timeout(
                base_url,
                AuthMethod::BearerToken(api_key),
                std::time::Duration::from_secs(600),
            )?;

            if let Ok(custom) = cfg.get_secret::<String>("MYCORP_CUSTOM_HEADER") {
                client = client.with_header("X-MyCorp-Custom", &custom)?;
            }

            Ok(Self {
                api_client: client,
                model,
                name: "mycorp".to_string(),
            })
        })
    }
}

```

### Step 4: Implement the `Provider` Trait for Runtime Logic

The `stream()` method builds the request payload, calls your API, and converts the response:

```rust
#[async_trait]
impl Provider for MyCorpProvider {
    fn get_name(&self) -> &str {
        &self.name
    }

    fn get_model_config(&self) -> ModelConfig {
        self.model.clone()
    }

    async fn stream(
        &self,
        model_config: &ModelConfig,
        session_id: &str,
        system: &str,
        messages: &[crate::conversation::message::Message],
        tools: &[Tool],
    ) -> Result<MessageStream, super::errors::ProviderError> {
        let payload = serde_json::json!({
            "model": model_config.model_name,
            "system": system,
            "messages": messages,
            "tools": tools,
            "stream": true,
        });

        let response = self
            .api_client
            .response_post(Some(session_id), "chat/completions", &payload)
            .await
            .map_err(|e| super::errors::ProviderError::RequestFailed(e.to_string()))?;

        // Parse response - adjust based on your API's format
        let json = response.json().await?;
        let message = super::formats::openai::response_to_message(&json)?;
        let usage = super::formats::openai::get_usage(
            json.get("usage").unwrap_or(&serde_json::Value::Null)
        );

        Ok(stream_from_single_message(message, ProviderUsage::new(
            model_config.model_name.clone(),
            usage
        )))
    }
}

```

### Step 5: Register Your Provider

Add your provider to the registry in [`crates/goose/src/providers/init.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/init.rs):

```rust
use crate::providers::mycorp::MyCorpProvider;

pub fn register_all(registry: &mut ProviderRegistry) {
    // Existing providers...
    registry.register::<MyCorpProvider>(true); // true = preferred (shown first)
}

```

## Alternative: Declarative Registration

If your API is **OpenAI-compatible**, you can skip Rust coding entirely. Create a JSON file at `$XDG_CONFIG_HOME/goose/custom_providers/mycorp.json`:

```json
{
  "name": "mycorp",
  "engine": "openai",
  "display_name": "MyCorp LLM",
  "description": "Proprietary hosted model",
  "api_key_env": "MYCORP_API_KEY",
  "base_url": "https://api.mycorp.com/v1",
  "models": [
    { "name": "mycorp-7b", "context_limit": 32768 }
  ],
  "headers": { "X-MyCorp-Custom": "true" },
  "supports_streaming": true
}

```

The declarative loader ([`declarative_providers.rs`](https://github.com/block/goose/blob/main/declarative_providers.rs) lines 60-71) automatically builds a provider using the OpenAI implementation with your custom endpoint and headers.

## Summary

- **Implement `ProviderDef`** in [`crates/goose/src/providers/base.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/base.rs) to define metadata and construction logic for your proprietary LLM.
- **Implement `Provider`** to handle the actual HTTP requests and response streaming via `ApiClient`.
- **Use `ConfigKey`** to declare environment variables and secrets that users must configure.
- **Register via `ProviderRegistry`** for compiled-in providers, or use JSON declarative configs for OpenAI-compatible endpoints without code changes.
- **Reference [`openai.rs`](https://github.com/block/goose/blob/main/openai.rs)** as the canonical implementation showing streaming, authentication, and response parsing patterns.

## Frequently Asked Questions

### Do I need to modify the core Goose codebase to add a custom provider?

For compiled-in providers, you must add a new file under `crates/goose/src/providers/` and register it in [`init.rs`](https://github.com/block/goose/blob/main/init.rs). However, if your proprietary API follows the OpenAI request/response format, you can use the **declarative JSON approach** by placing a configuration file in `$XDG_CONFIG_HOME/goose/custom_providers/` without touching any Rust code.

### How do I handle OAuth instead of API keys for authentication?

Use **`ConfigKey::new_oauth`** instead of the standard constructor in your `metadata()` function. You must then override the `configure_oauth()` method in your `Provider` implementation to handle the device-code or authorization-code flow, as the default implementation in [`base.rs`](https://github.com/block/goose/blob/main/base.rs) (lines 47-63) returns an error.

### What is the difference between programmatic and declarative provider registration?

**Programmatic registration** requires implementing the `Provider` and `ProviderDef` traits in Rust and calling `registry.register::<YourProvider>()`. This gives you full control over request formatting and response parsing. **Declarative registration** uses JSON files that the loader transforms into calls to `register_with_name`, leveraging existing engine implementations like OpenAI but with custom base URLs and headers.

### How do I test my custom provider without calling the live API?

Study **[`testprovider.rs`](https://github.com/block/goose/blob/main/testprovider.rs)** in the Goose repository, which implements a record/replay provider for testing. You can create a mock provider that implements the `Provider` trait to return static responses, or use the `ApiClient` configuration to point to a local test server during development.