Add ACP terminal-login via _meta field (#41954)
As discussed with @benbrandt and @mikayla-maki: * We now tell ACP clients we support the nonstandard `terminal-auth` `_meta` field for terminal-based authentication * In the future, we anticipate ACP itself supporting *some* form of terminal-based authentication, but that hasn't been designed yet or gone through the RFD process * For now, this unblocks terminal-based auth Release Notes: - Added experimental terminal-based authentication to ACP support
This commit is contained in:
@@ -178,6 +178,7 @@ impl AcpConnection {
|
||||
meta: Some(serde_json::json!({
|
||||
// Experimental: Allow for rendering terminal output from the agents
|
||||
"terminal_output": true,
|
||||
"terminal-auth": true,
|
||||
})),
|
||||
},
|
||||
client_info: Some(acp::Implementation {
|
||||
|
||||
@@ -1473,6 +1473,106 @@ impl AcpThreadView {
|
||||
return;
|
||||
};
|
||||
|
||||
// Check for the experimental "terminal-auth" _meta field
|
||||
let auth_method = connection.auth_methods().iter().find(|m| m.id == method);
|
||||
|
||||
if let Some(auth_method) = auth_method {
|
||||
if let Some(meta) = &auth_method.meta {
|
||||
if let Some(terminal_auth) = meta.get("terminal-auth") {
|
||||
// Extract terminal auth details from meta
|
||||
if let (Some(command), Some(label)) = (
|
||||
terminal_auth.get("command").and_then(|v| v.as_str()),
|
||||
terminal_auth.get("label").and_then(|v| v.as_str()),
|
||||
) {
|
||||
let args = terminal_auth
|
||||
.get("args")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|v| v.as_str().map(String::from))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let env = terminal_auth
|
||||
.get("env")
|
||||
.and_then(|v| v.as_object())
|
||||
.map(|obj| {
|
||||
obj.iter()
|
||||
.filter_map(|(k, v)| {
|
||||
v.as_str().map(|val| (k.clone(), val.to_string()))
|
||||
})
|
||||
.collect::<HashMap<String, String>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// Build SpawnInTerminal from _meta
|
||||
let login = task::SpawnInTerminal {
|
||||
id: task::TaskId(format!("external-agent-{}-login", label)),
|
||||
full_label: label.to_string(),
|
||||
label: label.to_string(),
|
||||
command: Some(command.to_string()),
|
||||
args,
|
||||
command_label: label.to_string(),
|
||||
env,
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: true,
|
||||
hide: task::HideStrategy::Always,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.thread_error.take();
|
||||
configuration_view.take();
|
||||
pending_auth_method.replace(method.clone());
|
||||
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
let authenticate = Self::spawn_external_agent_login(
|
||||
login, workspace, false, window, cx,
|
||||
);
|
||||
cx.notify();
|
||||
self.auth_task = Some(cx.spawn_in(window, {
|
||||
let agent = self.agent.clone();
|
||||
async move |this, cx| {
|
||||
let result = authenticate.await;
|
||||
|
||||
match &result {
|
||||
Ok(_) => telemetry::event!(
|
||||
"Authenticate Agent Succeeded",
|
||||
agent = agent.telemetry_id()
|
||||
),
|
||||
Err(_) => {
|
||||
telemetry::event!(
|
||||
"Authenticate Agent Failed",
|
||||
agent = agent.telemetry_id(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
if let Err(err) = result {
|
||||
if let ThreadState::Unauthenticated {
|
||||
pending_auth_method,
|
||||
..
|
||||
} = &mut this.thread_state
|
||||
{
|
||||
pending_auth_method.take();
|
||||
}
|
||||
this.handle_thread_error(err, cx);
|
||||
} else {
|
||||
this.reset(window, cx);
|
||||
}
|
||||
this.auth_task.take()
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if method.0.as_ref() == "gemini-api-key" {
|
||||
let registry = LanguageModelRegistry::global(cx);
|
||||
let provider = registry
|
||||
|
||||
Reference in New Issue
Block a user