Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(provider): add support for custom provider URLs #474

Merged
merged 4 commits into from
Mar 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ Forge is a comprehensive coding agent that integrates AI capabilities with your
- [Agent Configuration Options](#agent-configuration-options)
- [Built-in Templates](#built-in-templates)
- [Example Workflow Configuration](#example-workflow-configuration)
- [Provider Configuration](#provider-configuration)
- [Supported Providers](#supported-providers)
- [Custom Provider URLs](#custom-provider-urls)
- [Why Shell?](#why-shell)
- [Community](#community)
- [Support Us](#support-us)
Expand Down Expand Up @@ -86,6 +89,9 @@ wget -qO- https://raw.githubusercontent.com/antinomyhq/forge/main/install.sh | b
```bash
# Your API key for accessing AI models (see Environment Configuration section)
OPENROUTER_API_KEY=<Enter your Open Router Key>

# Optional: Set a custom URL for OpenAI-compatible providers
#OPENAI_URL=https://custom-openai-provider.com/v1
```

_You can get a Key at [Open Router](https://openrouter.ai/)_
Expand Down Expand Up @@ -239,6 +245,58 @@ tail -f /Users/tushar/Library/Application Support/forge/logs/forge.log.2025-03-0

This displays the logs in a nicely color-coded structure that's much easier to analyze, helping you quickly identify patterns, errors, or specific behavior during development and debugging.

## Provider Configuration

Forge supports multiple AI providers and allows custom configuration to meet your specific needs.

### Supported Providers

Forge automatically detects and uses your API keys from environment variables in the following priority order:

1. `FORGE_KEY` - Antinomy's provider (OpenAI-compatible)
2. `OPENROUTER_API_KEY` - Open Router provider (aggregates multiple models)
3. `OPENAI_API_KEY` - Official OpenAI provider
4. `ANTHROPIC_API_KEY` - Official Anthropic provider

To use a specific provider, set the corresponding environment variable in your `.env` file.

```bash
# Examples of different provider configurations (use only one)

# For Open Router (recommended, provides access to multiple models)
OPENROUTER_API_KEY=your_openrouter_key_here

# For official OpenAI
OPENAI_API_KEY=your_openai_key_here

# For official Anthropic
ANTHROPIC_API_KEY=your_anthropic_key_here

# For Antinomy's provider
FORGE_KEY=your_forge_key_here
```

### Custom Provider URLs

For OpenAI-compatible providers (including Open Router), you can customize the API endpoint URL by setting the `OPENAI_URL` environment variable:

```bash
# Custom OpenAI-compatible provider
OPENAI_API_KEY=your_api_key_here
OPENAI_URL=https://your-custom-provider.com/v1

# Or with Open Router but custom endpoint
OPENROUTER_API_KEY=your_openrouter_key_here
OPENAI_URL=https://alternative-openrouter-endpoint.com/v1
```

This is particularly useful when:

- Using self-hosted models with OpenAI-compatible APIs
- Connecting to enterprise OpenAI deployments
- Using proxy services or API gateways
- Working with regional API endpoints

## Custom Workflows and Multi-Agent Systems

For complex tasks, a single agent may not be sufficient. Forge allows you to create custom workflows with multiple specialized agents working together to accomplish sophisticated tasks.
Expand Down
9 changes: 6 additions & 3 deletions crates/forge_domain/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,12 @@ impl Context {
}

pub fn add_tool_results(mut self, results: Vec<ToolResult>) -> Self {
debug!(results = ?results, "Adding tool results to context");
self.messages
.extend(results.into_iter().map(ContextMessage::tool_result));
if !results.is_empty() {
debug!(results = ?results, "Adding tool results to context");
self.messages
.extend(results.into_iter().map(ContextMessage::tool_result));
}

self
}

Expand Down
18 changes: 14 additions & 4 deletions crates/forge_domain/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,38 @@ pub enum Provider {
}

impl Provider {
pub fn antinomy(key: impl Into<String>) -> Provider {
/// Sets the OpenAI URL if the provider is an OpenAI compatible provider
pub fn open_ai_url(&mut self, url: String) {
match self {
Provider::OpenAI { url: set_url, .. } => {
*set_url = Url::parse(&url).unwrap();
}
Provider::Anthropic { .. } => {}
}
}

pub fn antinomy(key: &str) -> Provider {
Provider::OpenAI {
url: Url::parse(Provider::ANTINOMY_URL).unwrap(),
key: Some(key.into()),
}
}

pub fn openai(key: impl Into<String>) -> Provider {
pub fn openai(key: &str) -> Provider {
Provider::OpenAI {
url: Url::parse(Provider::OPENAI_URL).unwrap(),
key: Some(key.into()),
}
}

pub fn open_router(key: impl Into<String>) -> Provider {
pub fn open_router(key: &str) -> Provider {
Provider::OpenAI {
url: Url::parse(Provider::OPEN_ROUTER_URL).unwrap(),
key: Some(key.into()),
}
}

pub fn anthropic(key: impl Into<String>) -> Provider {
pub fn anthropic(key: &str) -> Provider {
Provider::Anthropic { key: key.into() }
}

Expand Down
29 changes: 15 additions & 14 deletions crates/forge_infra/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,10 @@ impl ForgeEnvironmentService {
/// Panics if no API key is found in the environment
fn resolve_provider(&self) -> Provider {
let keys: [ProviderSearch; 4] = [
("FORGE_KEY", Box::new(|key: &str| Provider::antinomy(key))),
(
"OPENROUTER_API_KEY",
Box::new(|key: &str| Provider::open_router(key)),
),
(
"OPENAI_API_KEY",
Box::new(|key: &str| Provider::openai(key)),
),
(
"ANTHROPIC_API_KEY",
Box::new(|key: &str| Provider::anthropic(key)),
),
("FORGE_KEY", Box::new(Provider::antinomy)),
("OPENROUTER_API_KEY", Box::new(Provider::open_router)),
("OPENAI_API_KEY", Box::new(Provider::openai)),
("ANTHROPIC_API_KEY", Box::new(Provider::anthropic)),
];

let env_variables = keys
Expand All @@ -60,7 +51,17 @@ impl ForgeEnvironmentService {
.join(", ");

keys.into_iter()
.find_map(|(key, fun)| std::env::var(key).ok().map(|key| fun(&key)))
.find_map(|(key, fun)| {
std::env::var(key).ok().map(|key| {
let mut provider = fun(&key);

if let Ok(url) = std::env::var("OPENAI_URL") {
provider.open_ai_url(url);
}

provider
})
})
.unwrap_or_else(|| panic!("No API key found. Please set one of: {}", env_variables))
}

Expand Down
Loading