Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
20c2674
refactor: make agent provider and model fields required
ssddOnTop Nov 14, 2025
710f195
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 14, 2025
f74119d
refactor(agent): introduce AgentOrchestrator for agent lifecycle mana…
ssddOnTop Nov 14, 2025
520465a
refactor(agent): introduce AgentOrchestrator for agent lifecycle mana…
ssddOnTop Nov 14, 2025
edb160e
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 14, 2025
d421767
refactor(agent): extract AgentDefinition into separate module
ssddOnTop Nov 14, 2025
1d37cde
refactor(agent): consolidate AgentDefinition into Agent domain type
ssddOnTop Nov 14, 2025
f5a6f08
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 14, 2025
4e7117e
refactor(agent): replace AgentOrchestrator with AgentRepository pattern
ssddOnTop Nov 14, 2025
a4dd594
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 14, 2025
97bc2fc
refactor(api): separate default and agent-specific provider methods
ssddOnTop Nov 14, 2025
abcf363
refactor(agent): remove ForgeAgentRepository from public exports
ssddOnTop Nov 14, 2025
9f38bfc
refactor(info): expand agent display with provider and model details
ssddOnTop Nov 14, 2025
1df638f
refactor(agent): remove caching layer from agent repository
ssddOnTop Nov 14, 2025
ee5ea0c
refactor(info): add agent id field and model to agent display
ssddOnTop Nov 14, 2025
a0058ae
refactor(ui): display model names instead of ids in agent info
ssddOnTop Nov 14, 2025
ea1e2d9
refactor(ui): display model names instead of ids in agent info
ssddOnTop Nov 14, 2025
75262bf
Revert "refactor(ui): display model names instead of ids in agent info"
ssddOnTop Nov 14, 2025
e4b303a
Revert "refactor(ui): display model names instead of ids in agent info"
ssddOnTop Nov 14, 2025
5f12e40
refactor(ui): display model names instead of model ids in agent list
ssddOnTop Nov 14, 2025
a6d6c97
refactor(ui): align agent list layout with fixed-width columns
ssddOnTop Nov 14, 2025
3a5eab5
Merge branch 'main' into refactor/agent-definition
ssddOnTop Nov 16, 2025
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
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ nucleo = "0.5.0"
gray_matter = "0.3.2"
num-format = "0.4"
humantime = "2.1.0"

dashmap = "7.0.0-rc2"

# Internal crates
forge_api = { path = "crates/forge_api" }
Expand Down
12 changes: 5 additions & 7 deletions crates/forge_api/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};

use anyhow::Result;
use forge_app::dto::ToolsOverview;
use forge_app::{User, UserUsage};
use forge_app::{Agent, User, UserUsage};
use forge_domain::{AgentId, InitAuth, ModelId};
use forge_stream::MpscStream;
use url::Url;
Expand Down Expand Up @@ -123,11 +123,9 @@ pub trait API: Sync + Send {
/// Logs out the current user and clears authentication data
async fn logout(&self) -> anyhow::Result<()>;

/// Retrieves the provider configuration for the specified agent
async fn get_agent_provider(&self, agent_id: AgentId) -> anyhow::Result<Provider<Url>>;

/// Retrieves the provider configuration for the default agent
async fn get_default_provider(&self) -> anyhow::Result<Provider<Url>>;
/// Retrieves the provider configuration for the specified agent.
/// If agent_id is None, returns the default provider.
async fn get_agent_provider(&self, agent_id: Option<AgentId>) -> anyhow::Result<Provider<Url>>;

/// Sets the default provider for all the agents
async fn set_default_provider(&self, provider_id: ProviderId) -> anyhow::Result<()>;
Expand All @@ -145,7 +143,7 @@ pub trait API: Sync + Send {
async fn set_active_agent(&self, agent_id: AgentId) -> anyhow::Result<()>;

/// Gets the model for the specified agent
async fn get_agent_model(&self, agent_id: AgentId) -> Option<ModelId>;
async fn get_agent_model(&self, agent_id: Option<AgentId>) -> Option<ModelId>;

/// Gets the default model
async fn get_default_model(&self) -> Option<ModelId>;
Expand Down
22 changes: 9 additions & 13 deletions crates/forge_api/src/forge_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::time::Duration;
use anyhow::{Context, Result};
use forge_app::dto::ToolsOverview;
use forge_app::{
AgentProviderResolver, AgentRegistry, AppConfigService, AuthService, CommandInfra,
Agent, AgentProviderResolver, AgentRegistry, AppConfigService, AuthService, CommandInfra,
CommandLoaderService, ConversationService, EnvironmentInfra, EnvironmentService,
FileDiscoveryService, ForgeApp, GitApp, McpConfigManager, McpService, ProviderAuthService,
ProviderService, Services, User, UserUsage, Walker, WorkflowService,
Expand Down Expand Up @@ -63,14 +63,14 @@ impl<A: Services, F: CommandInfra + EnvironmentInfra> API for ForgeAPI<A, F> {
Ok(self
.services
.models(
self.get_default_provider()
self.get_agent_provider(None)
.await
.context("Failed to fetch models")?,
)
.await?)
}
async fn get_agents(&self) -> Result<Vec<Agent>> {
Ok(self.services.get_agents().await?)
self.services.get_agents().await
}

async fn get_providers(&self) -> Result<Vec<AnyProvider>> {
Expand Down Expand Up @@ -217,22 +217,18 @@ impl<A: Services, F: CommandInfra + EnvironmentInfra> API for ForgeAPI<A, F> {
async fn logout(&self) -> Result<()> {
self.app().logout().await
}
async fn get_agent_provider(&self, agent_id: AgentId) -> anyhow::Result<Provider<Url>> {
let agent_provider_resolver = AgentProviderResolver::new(self.services.clone());
agent_provider_resolver.get_provider(Some(agent_id)).await
}

async fn get_default_provider(&self) -> anyhow::Result<Provider<Url>> {
async fn get_agent_provider(&self, agent_id: Option<AgentId>) -> anyhow::Result<Provider<Url>> {
let agent_provider_resolver = AgentProviderResolver::new(self.services.clone());
agent_provider_resolver.get_provider(None).await
agent_provider_resolver.get_provider(agent_id).await
}

async fn set_default_provider(&self, provider_id: ProviderId) -> anyhow::Result<()> {
self.services.set_default_provider(provider_id).await
}

async fn user_info(&self) -> Result<Option<User>> {
let provider = self.get_default_provider().await?;
let provider = self.get_agent_provider(None).await?;
if let Some(api_key) = provider.api_key() {
let user_info = self.services.user_info(api_key.as_str()).await?;
return Ok(Some(user_info));
Expand All @@ -241,7 +237,7 @@ impl<A: Services, F: CommandInfra + EnvironmentInfra> API for ForgeAPI<A, F> {
}

async fn user_usage(&self) -> Result<Option<UserUsage>> {
let provider = self.get_default_provider().await?;
let provider = self.get_agent_provider(None).await?;
if let Some(api_key) = provider
.credential
.as_ref()
Expand All @@ -264,9 +260,9 @@ impl<A: Services, F: CommandInfra + EnvironmentInfra> API for ForgeAPI<A, F> {
self.services.set_active_agent_id(agent_id).await
}

async fn get_agent_model(&self, agent_id: AgentId) -> Option<ModelId> {
async fn get_agent_model(&self, agent_id: Option<AgentId>) -> Option<ModelId> {
let agent_provider_resolver = AgentProviderResolver::new(self.services.clone());
agent_provider_resolver.get_model(Some(agent_id)).await.ok()
agent_provider_resolver.get_model(agent_id).await.ok()
}

async fn get_default_model(&self) -> Option<ModelId> {
Expand Down
2 changes: 1 addition & 1 deletion crates/forge_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ mod forge_api;
pub use api::*;
pub use forge_api::*;
pub use forge_app::dto::*;
pub use forge_app::{Plan, UsageInfo, UserUsage};
pub use forge_app::{Agent, Plan, UsageInfo, UserUsage};
pub use forge_domain::*;
4 changes: 2 additions & 2 deletions crates/forge_app/src/agent.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::sync::Arc;

use forge_domain::{
Agent, ChatCompletionMessage, Context, Conversation, ModelId, ProviderId, ResultStream,
ChatCompletionMessage, Context, Conversation, ModelId, ProviderId, ResultStream,
ToolCallContext, ToolCallFull, ToolResult,
};

use crate::services::AppConfigService;
use crate::tool_registry::ToolRegistry;
use crate::{ConversationService, ProviderService, Services};
use crate::{Agent, ConversationService, ProviderService, Services};

/// Agent service trait that provides core chat and tool call functionality.
/// This trait abstracts the essential operations needed by the Orchestrator.
Expand Down
49 changes: 39 additions & 10 deletions crates/forge_app/src/agent_provider_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ where
/// agent is provided. Automatically refreshes OAuth credentials if they're
/// about to expire.
pub async fn get_provider(&self, agent_id: Option<AgentId>) -> Result<Provider<url::Url>> {
let provider = if let Some(agent) = agent_id
&& let Some(agent) = self.0.get_agent(&agent).await?
&& let Some(provider_id) = agent.provider
{
self.0.get_provider(provider_id).await?
let provider = if let Some(agent_id) = agent_id {
// Load all agent definitions and find the one we need

if let Some(agent) = self.0.get_agent(&agent_id).await? {
// If the agent definition has a provider, use it; otherwise use default
self.0.get_provider(agent.provider).await?
} else {
// TODO: Needs review, should we throw an err here?
// we can throw crate::Error::AgentNotFound
self.0.get_default_provider().await?
}
} else {
self.0.get_default_provider().await?
};
Expand Down Expand Up @@ -69,13 +75,36 @@ where
/// Gets the model for the specified agent, or the default model if no agent
/// is provided
pub async fn get_model(&self, agent_id: Option<AgentId>) -> Result<ModelId> {
let provider_id = self.get_provider(agent_id).await?.id;
self.0.get_default_model(&provider_id).await
if let Some(agent_id) = agent_id {
if let Some(agent) = self.0.get_agent(&agent_id).await? {
Ok(agent.model)
} else {
// TODO: Needs review, should we throw an err here?
// we can throw crate::Error::AgentNotFound
let provider_id = self.get_provider(Some(agent_id)).await?.id;
self.0.get_default_model(&provider_id).await
}
} else {
let provider_id = self.get_provider(None).await?.id;
self.0.get_default_model(&provider_id).await
}
}

/// Sets the default model for the agent's provider
/// Sets the model for the agent's provider
pub async fn set_default_model(&self, agent_id: Option<AgentId>, model: ModelId) -> Result<()> {
let provider_id = self.get_provider(agent_id).await?.id;
self.0.set_default_model(model, provider_id).await
if let Some(agent_id) = agent_id {
if let Some(agent) = self.0.get_agent(&agent_id).await? {
let provider_id = agent.provider;
self.0.set_default_model(model, provider_id).await
} else {
// TODO: Needs review, should we throw an err here?
// we can throw crate::Error::AgentNotFound
let provider_id = self.get_provider(None).await?.id;
self.0.set_default_model(model, provider_id).await
}
} else {
let provider_id = self.get_provider(None).await?.id;
self.0.set_default_model(model, provider_id).await
}
}
}
Loading
Loading