Skip to content

Commit 4e87011

Browse files
fix: lazy provider creation for goose acp (#7026) (#7066)
Signed-off-by: Adrian Cole <adrian@tetrate.io>
1 parent e670f34 commit 4e87011

8 files changed

Lines changed: 214 additions & 168 deletions

File tree

crates/goose-acp/src/bin/server.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anyhow::Result;
22
use clap::Parser;
3+
use goose::config::paths::Paths;
34
use goose_acp::server_factory::{AcpServer, AcpServerFactoryConfig};
45
use std::net::SocketAddr;
56
use std::sync::Arc;
@@ -36,12 +37,11 @@ async fn main() -> Result<()> {
3637
cli.builtins
3738
};
3839

39-
let config = AcpServerFactoryConfig {
40+
let server = Arc::new(AcpServer::new(AcpServerFactoryConfig {
4041
builtins,
41-
..Default::default()
42-
};
43-
44-
let server = Arc::new(AcpServer::new(config));
42+
data_dir: Paths::data_dir(),
43+
config_dir: Paths::config_dir(),
44+
}));
4545
let router = goose_acp::transport::create_router(server);
4646

4747
let addr: SocketAddr = format!("{}:{}", cli.host, cli.port).parse()?;

crates/goose-acp/src/server.rs

Lines changed: 59 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ use goose::conversation::Conversation;
1313
use goose::mcp_utils::ToolResult;
1414
use goose::permission::permission_confirmation::PrincipalType;
1515
use goose::permission::{Permission, PermissionConfirmation};
16-
use goose::providers::create;
16+
use goose::providers::provider_registry::ProviderConstructor;
1717
use goose::session::session_manager::SessionType;
1818
use goose::session::{Session, SessionManager};
1919
use rmcp::model::{CallToolResult, RawContent, ResourceContents, Role};
2020
use sacp::schema::{
21-
AgentCapabilities, AuthenticateRequest, AuthenticateResponse, BlobResourceContents,
21+
AgentCapabilities, AuthMethod, AuthenticateRequest, AuthenticateResponse, BlobResourceContents,
2222
CancelNotification, Content, ContentBlock, ContentChunk, EmbeddedResource,
2323
EmbeddedResourceResource, ImageContent, InitializeRequest, InitializeResponse,
2424
LoadSessionRequest, LoadSessionResponse, McpCapabilities, McpServer, NewSessionRequest,
@@ -46,16 +46,9 @@ struct GooseAcpSession {
4646
pub struct GooseAcpAgent {
4747
sessions: Arc<Mutex<HashMap<String, GooseAcpSession>>>,
4848
agent: Arc<Agent>,
49-
provider: Arc<dyn goose::providers::base::Provider>,
50-
}
51-
52-
pub struct AcpServerConfig {
53-
pub provider: Arc<dyn goose::providers::base::Provider>,
54-
pub builtins: Vec<String>,
55-
pub data_dir: std::path::PathBuf,
56-
pub config_dir: std::path::PathBuf,
57-
pub goose_mode: goose::config::GooseMode,
58-
pub disable_session_naming: bool,
49+
provider_factory: ProviderConstructor,
50+
config_dir: std::path::PathBuf,
51+
provider_initialized: tokio::sync::OnceCell<String>,
5952
}
6053

6154
fn mcp_server_to_extension_config(mcp_server: McpServer) -> Result<ExtensionConfig, String> {
@@ -278,54 +271,23 @@ impl GooseAcpAgent {
278271
Arc::clone(&self.agent.config.permission_manager)
279272
}
280273

281-
pub async fn new(builtins: Vec<String>) -> Result<Self> {
282-
let config = Config::global();
283-
284-
let provider_name: String = config
285-
.get_goose_provider()
286-
.map_err(|e| anyhow::anyhow!("No provider configured: {}", e))?;
287-
288-
let model_name: String = config
289-
.get_goose_model()
290-
.map_err(|e| anyhow::anyhow!("No model configured: {}", e))?;
291-
292-
let model_config = goose::model::ModelConfig {
293-
model_name: model_name.clone(),
294-
context_limit: None,
295-
temperature: None,
296-
max_tokens: None,
297-
toolshim: false,
298-
toolshim_model: None,
299-
fast_model: None,
300-
request_params: None,
301-
};
302-
let provider = create(&provider_name, model_config).await?;
303-
let goose_mode = config
304-
.get_goose_mode()
305-
.unwrap_or(goose::config::GooseMode::Auto);
306-
307-
Self::with_config(AcpServerConfig {
308-
provider,
309-
builtins,
310-
data_dir: Paths::data_dir(),
311-
config_dir: Paths::config_dir(),
312-
goose_mode,
313-
disable_session_naming: config.get_goose_disable_session_naming().unwrap_or(false),
314-
})
315-
.await
316-
}
317-
318-
pub async fn with_config(config: AcpServerConfig) -> Result<Self> {
319-
let session_manager = Arc::new(SessionManager::new(config.data_dir));
320-
let config_dir = config.config_dir.clone();
321-
let permission_manager = Arc::new(PermissionManager::new(config.config_dir));
274+
pub async fn new(
275+
provider_factory: ProviderConstructor,
276+
builtins: Vec<String>,
277+
data_dir: std::path::PathBuf,
278+
config_dir: std::path::PathBuf,
279+
goose_mode: goose::config::GooseMode,
280+
disable_session_naming: bool,
281+
) -> Result<Self> {
282+
let session_manager = Arc::new(SessionManager::new(data_dir));
283+
let permission_manager = Arc::new(PermissionManager::new(config_dir.clone()));
322284

323285
let agent = Agent::with_config(AgentConfig::new(
324286
Arc::clone(&session_manager),
325287
permission_manager,
326288
None,
327-
config.goose_mode,
328-
config.disable_session_naming,
289+
goose_mode,
290+
disable_session_naming,
329291
));
330292

331293
let agent_ptr = Arc::new(agent);
@@ -334,13 +296,15 @@ impl GooseAcpAgent {
334296
let config_file = Config::new(&config_path, "goose")?;
335297
let extensions = get_enabled_extensions_with_config(&config_file);
336298

337-
add_builtins(&agent_ptr, config.builtins).await;
299+
add_builtins(&agent_ptr, builtins).await;
338300
add_extensions(&agent_ptr, extensions).await;
339301

340302
Ok(Self {
341-
provider: config.provider.clone(),
342303
sessions: Arc::new(Mutex::new(HashMap::new())),
343304
agent: agent_ptr,
305+
provider_factory,
306+
config_dir,
307+
provider_initialized: tokio::sync::OnceCell::new(),
344308
})
345309
}
346310

@@ -354,9 +318,7 @@ impl GooseAcpAgent {
354318
)
355319
.await?;
356320

357-
self.agent
358-
.update_provider(self.provider.clone(), &goose_session.id)
359-
.await?;
321+
self.ensure_provider(&goose_session).await?;
360322

361323
let session = GooseAcpSession {
362324
messages: Conversation::new_unvalidated(Vec::new()),
@@ -692,7 +654,15 @@ impl GooseAcpAgent {
692654
.embedded_context(true),
693655
)
694656
.mcp_capabilities(McpCapabilities::new().http(true));
695-
Ok(InitializeResponse::new(args.protocol_version).agent_capabilities(capabilities))
657+
Ok(InitializeResponse::new(args.protocol_version)
658+
.agent_capabilities(capabilities)
659+
.auth_methods(vec![AuthMethod::new(
660+
"goose-provider",
661+
"Configure Provider",
662+
)
663+
.description(
664+
"Run `goose configure` to set up your AI provider and API key",
665+
)]))
696666
}
697667

698668
async fn on_new_session(
@@ -712,7 +682,9 @@ impl GooseAcpAgent {
712682
.map_err(|e| {
713683
sacp::Error::internal_error().data(format!("Failed to create session: {}", e))
714684
})?;
715-
self.update_session_with_provider(&goose_session).await?;
685+
self.ensure_provider(&goose_session).await.map_err(|e| {
686+
sacp::Error::internal_error().data(format!("Failed to set provider: {}", e))
687+
})?;
716688

717689
for mcp_server in args.mcp_servers {
718690
let config = match mcp_server_to_extension_config(mcp_server) {
@@ -746,16 +718,21 @@ impl GooseAcpAgent {
746718
Ok(NewSessionResponse::new(SessionId::new(goose_session.id)))
747719
}
748720

749-
async fn update_session_with_provider(
750-
&self,
751-
goose_session: &Session,
752-
) -> Result<(), sacp::Error> {
753-
self.agent
754-
.update_provider(self.provider.clone(), &goose_session.id)
755-
.await
756-
.map_err(|e| {
757-
sacp::Error::internal_error().data(format!("Failed to set provider: {}", e))
758-
})?;
721+
// Called at most once via OnceCell; returns the model_id used.
722+
async fn create_provider(&self, session: &Session) -> Result<String> {
723+
let config_path = self.config_dir.join(CONFIG_YAML_NAME);
724+
let config = Config::new(&config_path, "goose")?;
725+
let model_id = config.get_goose_model()?;
726+
let model_config = goose::model::ModelConfig::new(&model_id)?;
727+
let provider = (self.provider_factory)(model_config).await?;
728+
self.agent.update_provider(provider, &session.id).await?;
729+
Ok(model_id)
730+
}
731+
732+
async fn ensure_provider(&self, session: &Session) -> Result<()> {
733+
self.provider_initialized
734+
.get_or_try_init(|| self.create_provider(session))
735+
.await?;
759736
Ok(())
760737
}
761738

@@ -773,7 +750,9 @@ impl GooseAcpAgent {
773750
sacp::Error::invalid_params()
774751
.data(format!("Failed to load session {}: {}", session_id, e))
775752
})?;
776-
self.update_session_with_provider(&goose_session).await?;
753+
self.ensure_provider(&goose_session).await.map_err(|e| {
754+
sacp::Error::internal_error().data(format!("Failed to set provider: {}", e))
755+
})?;
777756

778757
let conversation = goose_session.conversation.ok_or_else(|| {
779758
sacp::Error::internal_error()
@@ -1045,7 +1024,13 @@ pub async fn run(builtins: Vec<String>) -> Result<()> {
10451024
let outgoing = tokio::io::stdout().compat_write();
10461025
let incoming = tokio::io::stdin().compat();
10471026

1048-
let agent = Arc::new(GooseAcpAgent::new(builtins).await?);
1027+
let server =
1028+
crate::server_factory::AcpServer::new(crate::server_factory::AcpServerFactoryConfig {
1029+
builtins,
1030+
data_dir: Paths::data_dir(),
1031+
config_dir: Paths::config_dir(),
1032+
});
1033+
let agent = server.create_agent().await?;
10491034
serve(agent, incoming, outgoing).await
10501035
}
10511036

crates/goose-acp/src/server_factory.rs

Lines changed: 31 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
11
use anyhow::Result;
2-
use goose::config::paths::Paths;
3-
use goose::config::Config;
4-
use goose::model::ModelConfig;
5-
use goose::providers::create;
2+
use goose::providers::provider_registry::ProviderConstructor;
63
use std::sync::Arc;
74
use tracing::info;
85

9-
use crate::server::{AcpServerConfig, GooseAcpAgent};
6+
use crate::server::GooseAcpAgent;
107

118
pub struct AcpServerFactoryConfig {
129
pub builtins: Vec<String>,
1310
pub data_dir: std::path::PathBuf,
1411
pub config_dir: std::path::PathBuf,
1512
}
1613

17-
impl Default for AcpServerFactoryConfig {
18-
fn default() -> Self {
19-
Self {
20-
builtins: vec!["developer".to_string()],
21-
data_dir: Paths::data_dir(),
22-
config_dir: Paths::config_dir(),
23-
}
24-
}
25-
}
26-
2714
pub struct AcpServer {
2815
config: AcpServerFactoryConfig,
2916
}
@@ -34,44 +21,39 @@ impl AcpServer {
3421
}
3522

3623
pub async fn create_agent(&self) -> Result<Arc<GooseAcpAgent>> {
37-
let global_config = Config::global();
38-
39-
let provider_name: String = global_config
40-
.get_goose_provider()
41-
.map_err(|e| anyhow::anyhow!("No provider configured: {}", e))?;
24+
let config_path = self
25+
.config
26+
.config_dir
27+
.join(goose::config::base::CONFIG_YAML_NAME);
28+
let config = goose::config::Config::new(&config_path, "goose")?;
4229

43-
let model_name: String = global_config
44-
.get_goose_model()
45-
.map_err(|e| anyhow::anyhow!("No model configured: {}", e))?;
46-
47-
let model_config = ModelConfig {
48-
request_params: None,
49-
model_name: model_name.clone(),
50-
context_limit: None,
51-
temperature: None,
52-
max_tokens: None,
53-
toolshim: false,
54-
toolshim_model: None,
55-
fast_model: None,
56-
};
57-
58-
let provider = create(&provider_name, model_config).await?;
59-
let goose_mode = global_config
30+
let goose_mode = config
6031
.get_goose_mode()
6132
.unwrap_or(goose::config::GooseMode::Auto);
62-
63-
let acp_config = AcpServerConfig {
64-
provider,
65-
builtins: self.config.builtins.clone(),
66-
data_dir: self.config.data_dir.clone(),
67-
config_dir: self.config.config_dir.clone(),
33+
let disable_session_naming = config.get_goose_disable_session_naming().unwrap_or(false);
34+
35+
let config_dir = self.config.config_dir.clone();
36+
let provider_factory: ProviderConstructor = Arc::new(move |model_config| {
37+
let config_dir = config_dir.clone();
38+
Box::pin(async move {
39+
let config_path = config_dir.join(goose::config::base::CONFIG_YAML_NAME);
40+
let config = goose::config::Config::new(&config_path, "goose")?;
41+
let provider_name = config
42+
.get_goose_provider()
43+
.map_err(|_| anyhow::anyhow!("No provider configured"))?;
44+
goose::providers::create(&provider_name, model_config).await
45+
})
46+
});
47+
48+
let agent = GooseAcpAgent::new(
49+
provider_factory,
50+
self.config.builtins.clone(),
51+
self.config.data_dir.clone(),
52+
self.config.config_dir.clone(),
6853
goose_mode,
69-
disable_session_naming: global_config
70-
.get_goose_disable_session_naming()
71-
.unwrap_or(false),
72-
};
73-
74-
let agent = GooseAcpAgent::with_config(acp_config).await?;
54+
disable_session_naming,
55+
)
56+
.await?;
7557
info!("Created new ACP agent");
7658

7759
Ok(Arc::new(agent))

crates/goose-acp/tests/common_tests/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ pub async fn run_configured_extension<S: Session>() {
189189
let mcp = McpFixture::new(expected_session_id.clone()).await;
190190

191191
let config_yaml = format!(
192-
"extensions:\n lookup:\n enabled: true\n type: streamable_http\n name: lookup\n description: Lookup server\n uri: \"{}\"\n",
192+
"GOOSE_MODEL: gpt-5-nano\nextensions:\n lookup:\n enabled: true\n type: streamable_http\n name: lookup\n description: Lookup server\n uri: \"{}\"\n",
193193
mcp.url
194194
);
195195
fs::write(temp_dir.path().join(CONFIG_YAML_NAME), config_yaml).unwrap();

0 commit comments

Comments
 (0)