since_v0_8_0.rs

   1use crate::wasm_host::wit::since_v0_8_0::{
   2    dap::{
   3        BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, StartDebuggingRequestArguments,
   4        TcpArguments, TcpArgumentsTemplate,
   5    },
   6    lsp::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind},
   7    slash_command::SlashCommandOutputSection,
   8};
   9use crate::wasm_host::{WasmState, wit::ToWasmtimeResult};
  10use ::http_client::{AsyncBody, HttpRequestExt};
  11use ::settings::{ModelMode, Settings, SettingsStore, WorktreeId};
  12use anyhow::{Context as _, Result, bail};
  13use async_compression::futures::bufread::GzipDecoder;
  14use async_tar::Archive;
  15use async_trait::async_trait;
  16use credentials_provider::CredentialsProvider;
  17use extension::{
  18    ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
  19};
  20use futures::{AsyncReadExt, lock::Mutex};
  21use futures::{FutureExt as _, io::BufReader};
  22use gpui::{BackgroundExecutor, SharedString};
  23use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings};
  24use project::project_settings::ProjectSettings;
  25use semver::Version;
  26use smol::net::TcpListener;
  27use std::{
  28    env,
  29    net::Ipv4Addr,
  30    path::{Path, PathBuf},
  31    str::FromStr,
  32    sync::{Arc, OnceLock},
  33    time::Duration,
  34};
  35use task::{SpawnInTerminal, ZedDebugConfig};
  36use url::Url;
  37use util::{
  38    archive::extract_zip, fs::make_file_executable, maybe, paths::PathStyle, rel_path::RelPath,
  39};
  40use wasmtime::component::{Linker, Resource};
  41
  42pub const MIN_VERSION: Version = Version::new(0, 8, 0);
  43pub const MAX_VERSION: Version = Version::new(0, 8, 0);
  44
  45wasmtime::component::bindgen!({
  46    async: true,
  47    trappable_imports: true,
  48    path: "../extension_api/wit/since_v0.8.0",
  49    with: {
  50         "worktree": ExtensionWorktree,
  51         "project": ExtensionProject,
  52         "key-value-store": ExtensionKeyValueStore,
  53         "zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
  54    },
  55});
  56
  57pub use self::zed::extension::*;
  58
  59mod settings {
  60    #![allow(dead_code)]
  61    include!(concat!(env!("OUT_DIR"), "/since_v0.8.0/settings.rs"));
  62}
  63
  64pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
  65pub type ExtensionProject = Arc<dyn ProjectDelegate>;
  66pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
  67pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
  68
  69pub fn linker(executor: &BackgroundExecutor) -> &'static Linker<WasmState> {
  70    static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
  71    LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker))
  72}
  73
  74impl From<Range> for std::ops::Range<usize> {
  75    fn from(range: Range) -> Self {
  76        let start = range.start as usize;
  77        let end = range.end as usize;
  78        start..end
  79    }
  80}
  81
  82impl From<Command> for extension::Command {
  83    fn from(value: Command) -> Self {
  84        Self {
  85            command: value.command.into(),
  86            args: value.args,
  87            env: value.env,
  88        }
  89    }
  90}
  91
  92impl From<StartDebuggingRequestArgumentsRequest>
  93    for extension::StartDebuggingRequestArgumentsRequest
  94{
  95    fn from(value: StartDebuggingRequestArgumentsRequest) -> Self {
  96        match value {
  97            StartDebuggingRequestArgumentsRequest::Launch => Self::Launch,
  98            StartDebuggingRequestArgumentsRequest::Attach => Self::Attach,
  99        }
 100    }
 101}
 102impl TryFrom<StartDebuggingRequestArguments> for extension::StartDebuggingRequestArguments {
 103    type Error = anyhow::Error;
 104
 105    fn try_from(value: StartDebuggingRequestArguments) -> Result<Self, Self::Error> {
 106        Ok(Self {
 107            configuration: serde_json::from_str(&value.configuration)?,
 108            request: value.request.into(),
 109        })
 110    }
 111}
 112impl From<TcpArguments> for extension::TcpArguments {
 113    fn from(value: TcpArguments) -> Self {
 114        Self {
 115            host: value.host.into(),
 116            port: value.port,
 117            timeout: value.timeout,
 118        }
 119    }
 120}
 121
 122impl From<extension::TcpArgumentsTemplate> for TcpArgumentsTemplate {
 123    fn from(value: extension::TcpArgumentsTemplate) -> Self {
 124        Self {
 125            host: value.host.map(Ipv4Addr::to_bits),
 126            port: value.port,
 127            timeout: value.timeout,
 128        }
 129    }
 130}
 131
 132impl From<TcpArgumentsTemplate> for extension::TcpArgumentsTemplate {
 133    fn from(value: TcpArgumentsTemplate) -> Self {
 134        Self {
 135            host: value.host.map(Ipv4Addr::from_bits),
 136            port: value.port,
 137            timeout: value.timeout,
 138        }
 139    }
 140}
 141
 142impl TryFrom<extension::DebugTaskDefinition> for DebugTaskDefinition {
 143    type Error = anyhow::Error;
 144    fn try_from(value: extension::DebugTaskDefinition) -> Result<Self, Self::Error> {
 145        Ok(Self {
 146            label: value.label.to_string(),
 147            adapter: value.adapter.to_string(),
 148            config: value.config.to_string(),
 149            tcp_connection: value.tcp_connection.map(Into::into),
 150        })
 151    }
 152}
 153
 154impl From<task::DebugRequest> for DebugRequest {
 155    fn from(value: task::DebugRequest) -> Self {
 156        match value {
 157            task::DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()),
 158            task::DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()),
 159        }
 160    }
 161}
 162
 163impl From<DebugRequest> for task::DebugRequest {
 164    fn from(value: DebugRequest) -> Self {
 165        match value {
 166            DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()),
 167            DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()),
 168        }
 169    }
 170}
 171
 172impl From<task::LaunchRequest> for LaunchRequest {
 173    fn from(value: task::LaunchRequest) -> Self {
 174        Self {
 175            program: value.program,
 176            cwd: value.cwd.map(|p| p.to_string_lossy().into_owned()),
 177            args: value.args,
 178            envs: value.env.into_iter().collect(),
 179        }
 180    }
 181}
 182
 183impl From<task::AttachRequest> for AttachRequest {
 184    fn from(value: task::AttachRequest) -> Self {
 185        Self {
 186            process_id: value.process_id,
 187        }
 188    }
 189}
 190
 191impl From<LaunchRequest> for task::LaunchRequest {
 192    fn from(value: LaunchRequest) -> Self {
 193        Self {
 194            program: value.program,
 195            cwd: value.cwd.map(|p| p.into()),
 196            args: value.args,
 197            env: value.envs.into_iter().collect(),
 198        }
 199    }
 200}
 201impl From<AttachRequest> for task::AttachRequest {
 202    fn from(value: AttachRequest) -> Self {
 203        Self {
 204            process_id: value.process_id,
 205        }
 206    }
 207}
 208
 209impl From<ZedDebugConfig> for DebugConfig {
 210    fn from(value: ZedDebugConfig) -> Self {
 211        Self {
 212            label: value.label.into(),
 213            adapter: value.adapter.into(),
 214            request: value.request.into(),
 215            stop_on_entry: value.stop_on_entry,
 216        }
 217    }
 218}
 219impl TryFrom<DebugAdapterBinary> for extension::DebugAdapterBinary {
 220    type Error = anyhow::Error;
 221    fn try_from(value: DebugAdapterBinary) -> Result<Self, Self::Error> {
 222        Ok(Self {
 223            command: value.command,
 224            arguments: value.arguments,
 225            envs: value.envs.into_iter().collect(),
 226            cwd: value.cwd.map(|s| s.into()),
 227            connection: value.connection.map(Into::into),
 228            request_args: value.request_args.try_into()?,
 229        })
 230    }
 231}
 232
 233impl From<BuildTaskDefinition> for extension::BuildTaskDefinition {
 234    fn from(value: BuildTaskDefinition) -> Self {
 235        match value {
 236            BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
 237            BuildTaskDefinition::Template(build_task_template) => Self::Template {
 238                task_template: build_task_template.template.into(),
 239                locator_name: build_task_template.locator_name.map(SharedString::from),
 240            },
 241        }
 242    }
 243}
 244
 245impl From<extension::BuildTaskDefinition> for BuildTaskDefinition {
 246    fn from(value: extension::BuildTaskDefinition) -> Self {
 247        match value {
 248            extension::BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
 249            extension::BuildTaskDefinition::Template {
 250                task_template,
 251                locator_name,
 252            } => Self::Template(BuildTaskDefinitionTemplatePayload {
 253                template: task_template.into(),
 254                locator_name: locator_name.map(String::from),
 255            }),
 256        }
 257    }
 258}
 259impl From<BuildTaskTemplate> for extension::BuildTaskTemplate {
 260    fn from(value: BuildTaskTemplate) -> Self {
 261        Self {
 262            label: value.label,
 263            command: value.command,
 264            args: value.args,
 265            env: value.env.into_iter().collect(),
 266            cwd: value.cwd,
 267            ..Default::default()
 268        }
 269    }
 270}
 271impl From<extension::BuildTaskTemplate> for BuildTaskTemplate {
 272    fn from(value: extension::BuildTaskTemplate) -> Self {
 273        Self {
 274            label: value.label,
 275            command: value.command,
 276            args: value.args,
 277            env: value.env.into_iter().collect(),
 278            cwd: value.cwd,
 279        }
 280    }
 281}
 282
 283impl TryFrom<DebugScenario> for extension::DebugScenario {
 284    type Error = anyhow::Error;
 285
 286    fn try_from(value: DebugScenario) -> std::result::Result<Self, Self::Error> {
 287        Ok(Self {
 288            adapter: value.adapter.into(),
 289            label: value.label.into(),
 290            build: value.build.map(Into::into),
 291            config: serde_json::Value::from_str(&value.config)?,
 292            tcp_connection: value.tcp_connection.map(Into::into),
 293        })
 294    }
 295}
 296
 297impl From<extension::DebugScenario> for DebugScenario {
 298    fn from(value: extension::DebugScenario) -> Self {
 299        Self {
 300            adapter: value.adapter.into(),
 301            label: value.label.into(),
 302            build: value.build.map(Into::into),
 303            config: value.config.to_string(),
 304            tcp_connection: value.tcp_connection.map(Into::into),
 305        }
 306    }
 307}
 308
 309impl TryFrom<SpawnInTerminal> for ResolvedTask {
 310    type Error = anyhow::Error;
 311
 312    fn try_from(value: SpawnInTerminal) -> Result<Self, Self::Error> {
 313        Ok(Self {
 314            label: value.label,
 315            command: value.command.context("missing command")?,
 316            args: value.args,
 317            env: value.env.into_iter().collect(),
 318            cwd: value.cwd.map(|s| {
 319                let s = s.to_string_lossy();
 320                if cfg!(target_os = "windows") {
 321                    s.replace('\\', "/")
 322                } else {
 323                    s.into_owned()
 324                }
 325            }),
 326        })
 327    }
 328}
 329
 330impl From<CodeLabel> for extension::CodeLabel {
 331    fn from(value: CodeLabel) -> Self {
 332        Self {
 333            code: value.code,
 334            spans: value.spans.into_iter().map(Into::into).collect(),
 335            filter_range: value.filter_range.into(),
 336        }
 337    }
 338}
 339
 340impl From<CodeLabelSpan> for extension::CodeLabelSpan {
 341    fn from(value: CodeLabelSpan) -> Self {
 342        match value {
 343            CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
 344            CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
 345        }
 346    }
 347}
 348
 349impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
 350    fn from(value: CodeLabelSpanLiteral) -> Self {
 351        Self {
 352            text: value.text,
 353            highlight_name: value.highlight_name,
 354        }
 355    }
 356}
 357
 358impl From<extension::Completion> for Completion {
 359    fn from(value: extension::Completion) -> Self {
 360        Self {
 361            label: value.label,
 362            label_details: value.label_details.map(Into::into),
 363            detail: value.detail,
 364            kind: value.kind.map(Into::into),
 365            insert_text_format: value.insert_text_format.map(Into::into),
 366        }
 367    }
 368}
 369
 370impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
 371    fn from(value: extension::CompletionLabelDetails) -> Self {
 372        Self {
 373            detail: value.detail,
 374            description: value.description,
 375        }
 376    }
 377}
 378
 379impl From<extension::CompletionKind> for CompletionKind {
 380    fn from(value: extension::CompletionKind) -> Self {
 381        match value {
 382            extension::CompletionKind::Text => Self::Text,
 383            extension::CompletionKind::Method => Self::Method,
 384            extension::CompletionKind::Function => Self::Function,
 385            extension::CompletionKind::Constructor => Self::Constructor,
 386            extension::CompletionKind::Field => Self::Field,
 387            extension::CompletionKind::Variable => Self::Variable,
 388            extension::CompletionKind::Class => Self::Class,
 389            extension::CompletionKind::Interface => Self::Interface,
 390            extension::CompletionKind::Module => Self::Module,
 391            extension::CompletionKind::Property => Self::Property,
 392            extension::CompletionKind::Unit => Self::Unit,
 393            extension::CompletionKind::Value => Self::Value,
 394            extension::CompletionKind::Enum => Self::Enum,
 395            extension::CompletionKind::Keyword => Self::Keyword,
 396            extension::CompletionKind::Snippet => Self::Snippet,
 397            extension::CompletionKind::Color => Self::Color,
 398            extension::CompletionKind::File => Self::File,
 399            extension::CompletionKind::Reference => Self::Reference,
 400            extension::CompletionKind::Folder => Self::Folder,
 401            extension::CompletionKind::EnumMember => Self::EnumMember,
 402            extension::CompletionKind::Constant => Self::Constant,
 403            extension::CompletionKind::Struct => Self::Struct,
 404            extension::CompletionKind::Event => Self::Event,
 405            extension::CompletionKind::Operator => Self::Operator,
 406            extension::CompletionKind::TypeParameter => Self::TypeParameter,
 407            extension::CompletionKind::Other(value) => Self::Other(value),
 408        }
 409    }
 410}
 411
 412impl From<extension::InsertTextFormat> for InsertTextFormat {
 413    fn from(value: extension::InsertTextFormat) -> Self {
 414        match value {
 415            extension::InsertTextFormat::PlainText => Self::PlainText,
 416            extension::InsertTextFormat::Snippet => Self::Snippet,
 417            extension::InsertTextFormat::Other(value) => Self::Other(value),
 418        }
 419    }
 420}
 421
 422impl From<extension::Symbol> for Symbol {
 423    fn from(value: extension::Symbol) -> Self {
 424        Self {
 425            kind: value.kind.into(),
 426            name: value.name,
 427        }
 428    }
 429}
 430
 431impl From<extension::SymbolKind> for SymbolKind {
 432    fn from(value: extension::SymbolKind) -> Self {
 433        match value {
 434            extension::SymbolKind::File => Self::File,
 435            extension::SymbolKind::Module => Self::Module,
 436            extension::SymbolKind::Namespace => Self::Namespace,
 437            extension::SymbolKind::Package => Self::Package,
 438            extension::SymbolKind::Class => Self::Class,
 439            extension::SymbolKind::Method => Self::Method,
 440            extension::SymbolKind::Property => Self::Property,
 441            extension::SymbolKind::Field => Self::Field,
 442            extension::SymbolKind::Constructor => Self::Constructor,
 443            extension::SymbolKind::Enum => Self::Enum,
 444            extension::SymbolKind::Interface => Self::Interface,
 445            extension::SymbolKind::Function => Self::Function,
 446            extension::SymbolKind::Variable => Self::Variable,
 447            extension::SymbolKind::Constant => Self::Constant,
 448            extension::SymbolKind::String => Self::String,
 449            extension::SymbolKind::Number => Self::Number,
 450            extension::SymbolKind::Boolean => Self::Boolean,
 451            extension::SymbolKind::Array => Self::Array,
 452            extension::SymbolKind::Object => Self::Object,
 453            extension::SymbolKind::Key => Self::Key,
 454            extension::SymbolKind::Null => Self::Null,
 455            extension::SymbolKind::EnumMember => Self::EnumMember,
 456            extension::SymbolKind::Struct => Self::Struct,
 457            extension::SymbolKind::Event => Self::Event,
 458            extension::SymbolKind::Operator => Self::Operator,
 459            extension::SymbolKind::TypeParameter => Self::TypeParameter,
 460            extension::SymbolKind::Other(value) => Self::Other(value),
 461        }
 462    }
 463}
 464
 465impl From<extension::SlashCommand> for SlashCommand {
 466    fn from(value: extension::SlashCommand) -> Self {
 467        Self {
 468            name: value.name,
 469            description: value.description,
 470            tooltip_text: value.tooltip_text,
 471            requires_argument: value.requires_argument,
 472        }
 473    }
 474}
 475
 476impl From<SlashCommandOutput> for extension::SlashCommandOutput {
 477    fn from(value: SlashCommandOutput) -> Self {
 478        Self {
 479            text: value.text,
 480            sections: value.sections.into_iter().map(Into::into).collect(),
 481        }
 482    }
 483}
 484
 485impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
 486    fn from(value: SlashCommandOutputSection) -> Self {
 487        Self {
 488            range: value.range.start as usize..value.range.end as usize,
 489            label: value.label,
 490        }
 491    }
 492}
 493
 494impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
 495    fn from(value: SlashCommandArgumentCompletion) -> Self {
 496        Self {
 497            label: value.label,
 498            new_text: value.new_text,
 499            run_command: value.run_command,
 500        }
 501    }
 502}
 503
 504impl TryFrom<ContextServerConfiguration> for extension::ContextServerConfiguration {
 505    type Error = anyhow::Error;
 506
 507    fn try_from(value: ContextServerConfiguration) -> Result<Self, Self::Error> {
 508        let settings_schema: serde_json::Value = serde_json::from_str(&value.settings_schema)
 509            .context("Failed to parse settings_schema")?;
 510
 511        Ok(Self {
 512            installation_instructions: value.installation_instructions,
 513            default_settings: value.default_settings,
 514            settings_schema,
 515        })
 516    }
 517}
 518
 519impl HostKeyValueStore for WasmState {
 520    async fn insert(
 521        &mut self,
 522        kv_store: Resource<ExtensionKeyValueStore>,
 523        key: String,
 524        value: String,
 525    ) -> wasmtime::Result<Result<(), String>> {
 526        let kv_store = self.table.get(&kv_store)?;
 527        kv_store.insert(key, value).await.to_wasmtime_result()
 528    }
 529
 530    async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
 531        // We only ever hand out borrows of key-value stores.
 532        Ok(())
 533    }
 534}
 535
 536impl HostProject for WasmState {
 537    async fn worktree_ids(
 538        &mut self,
 539        project: Resource<ExtensionProject>,
 540    ) -> wasmtime::Result<Vec<u64>> {
 541        let project = self.table.get(&project)?;
 542        Ok(project.worktree_ids())
 543    }
 544
 545    async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
 546        // We only ever hand out borrows of projects.
 547        Ok(())
 548    }
 549}
 550
 551impl HostWorktree for WasmState {
 552    async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
 553        let delegate = self.table.get(&delegate)?;
 554        Ok(delegate.id())
 555    }
 556
 557    async fn root_path(
 558        &mut self,
 559        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 560    ) -> wasmtime::Result<String> {
 561        let delegate = self.table.get(&delegate)?;
 562        Ok(delegate.root_path())
 563    }
 564
 565    async fn read_text_file(
 566        &mut self,
 567        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 568        path: String,
 569    ) -> wasmtime::Result<Result<String, String>> {
 570        let delegate = self.table.get(&delegate)?;
 571        Ok(delegate
 572            .read_text_file(&RelPath::new(Path::new(&path), PathStyle::Posix)?)
 573            .await
 574            .map_err(|error| error.to_string()))
 575    }
 576
 577    async fn shell_env(
 578        &mut self,
 579        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 580    ) -> wasmtime::Result<EnvVars> {
 581        let delegate = self.table.get(&delegate)?;
 582        Ok(delegate.shell_env().await.into_iter().collect())
 583    }
 584
 585    async fn which(
 586        &mut self,
 587        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 588        binary_name: String,
 589    ) -> wasmtime::Result<Option<String>> {
 590        let delegate = self.table.get(&delegate)?;
 591        Ok(delegate.which(binary_name).await)
 592    }
 593
 594    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
 595        // We only ever hand out borrows of worktrees.
 596        Ok(())
 597    }
 598}
 599
 600impl common::Host for WasmState {}
 601
 602impl http_client::Host for WasmState {
 603    async fn fetch(
 604        &mut self,
 605        request: http_client::HttpRequest,
 606    ) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
 607        maybe!(async {
 608            let url = &request.url;
 609            let request = convert_request(&request)?;
 610            let mut response = self.host.http_client.send(request).await?;
 611
 612            if response.status().is_client_error() || response.status().is_server_error() {
 613                bail!("failed to fetch '{url}': status code {}", response.status())
 614            }
 615            convert_response(&mut response).await
 616        })
 617        .await
 618        .to_wasmtime_result()
 619    }
 620
 621    async fn fetch_fallible(
 622        &mut self,
 623        request: http_client::HttpRequest,
 624    ) -> wasmtime::Result<Result<http_client::HttpResponseWithStatus, String>> {
 625        maybe!(async {
 626            let request = convert_request(&request)?;
 627            let mut response = self.host.http_client.send(request).await?;
 628            convert_response_with_status(&mut response).await
 629        })
 630        .await
 631        .to_wasmtime_result()
 632    }
 633
 634    async fn fetch_stream(
 635        &mut self,
 636        request: http_client::HttpRequest,
 637    ) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
 638        let request = convert_request(&request)?;
 639        let response = self.host.http_client.send(request);
 640        maybe!(async {
 641            let response = response.await?;
 642            let stream = Arc::new(Mutex::new(response));
 643            let resource = self.table.push(stream)?;
 644            Ok(resource)
 645        })
 646        .await
 647        .to_wasmtime_result()
 648    }
 649}
 650
 651impl http_client::HostHttpResponseStream for WasmState {
 652    async fn next_chunk(
 653        &mut self,
 654        resource: Resource<ExtensionHttpResponseStream>,
 655    ) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
 656        let stream = self.table.get(&resource)?.clone();
 657        maybe!(async move {
 658            let mut response = stream.lock().await;
 659            let mut buffer = vec![0; 8192]; // 8KB buffer
 660            let bytes_read = response.body_mut().read(&mut buffer).await?;
 661            if bytes_read == 0 {
 662                Ok(None)
 663            } else {
 664                buffer.truncate(bytes_read);
 665                Ok(Some(buffer))
 666            }
 667        })
 668        .await
 669        .to_wasmtime_result()
 670    }
 671
 672    async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
 673        Ok(())
 674    }
 675}
 676
 677impl From<http_client::HttpMethod> for ::http_client::Method {
 678    fn from(value: http_client::HttpMethod) -> Self {
 679        match value {
 680            http_client::HttpMethod::Get => Self::GET,
 681            http_client::HttpMethod::Post => Self::POST,
 682            http_client::HttpMethod::Put => Self::PUT,
 683            http_client::HttpMethod::Delete => Self::DELETE,
 684            http_client::HttpMethod::Head => Self::HEAD,
 685            http_client::HttpMethod::Options => Self::OPTIONS,
 686            http_client::HttpMethod::Patch => Self::PATCH,
 687        }
 688    }
 689}
 690
 691fn convert_request(
 692    extension_request: &http_client::HttpRequest,
 693) -> anyhow::Result<::http_client::Request<AsyncBody>> {
 694    let mut request = ::http_client::Request::builder()
 695        .method(::http_client::Method::from(extension_request.method))
 696        .uri(&extension_request.url)
 697        .follow_redirects(match extension_request.redirect_policy {
 698            http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
 699            http_client::RedirectPolicy::FollowLimit(limit) => {
 700                ::http_client::RedirectPolicy::FollowLimit(limit)
 701            }
 702            http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
 703        });
 704    for (key, value) in &extension_request.headers {
 705        request = request.header(key, value);
 706    }
 707    let body = extension_request
 708        .body
 709        .clone()
 710        .map(AsyncBody::from)
 711        .unwrap_or_default();
 712    request.body(body).map_err(anyhow::Error::from)
 713}
 714
 715async fn convert_response(
 716    response: &mut ::http_client::Response<AsyncBody>,
 717) -> anyhow::Result<http_client::HttpResponse> {
 718    let mut extension_response = http_client::HttpResponse {
 719        body: Vec::new(),
 720        headers: Vec::new(),
 721    };
 722
 723    for (key, value) in response.headers() {
 724        extension_response
 725            .headers
 726            .push((key.to_string(), value.to_str().unwrap_or("").to_string()));
 727    }
 728
 729    response
 730        .body_mut()
 731        .read_to_end(&mut extension_response.body)
 732        .await?;
 733
 734    Ok(extension_response)
 735}
 736
 737async fn convert_response_with_status(
 738    response: &mut ::http_client::Response<AsyncBody>,
 739) -> anyhow::Result<http_client::HttpResponseWithStatus> {
 740    let status = response.status().as_u16();
 741    let headers: Vec<(String, String)> = response
 742        .headers()
 743        .iter()
 744        .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
 745        .collect();
 746
 747    let mut body = Vec::new();
 748    response.body_mut().read_to_end(&mut body).await?;
 749
 750    Ok(http_client::HttpResponseWithStatus {
 751        status,
 752        headers,
 753        body,
 754    })
 755}
 756
 757impl nodejs::Host for WasmState {
 758    async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
 759        self.host
 760            .node_runtime
 761            .binary_path()
 762            .await
 763            .map(|path| path.to_string_lossy().into_owned())
 764            .to_wasmtime_result()
 765    }
 766
 767    async fn npm_package_latest_version(
 768        &mut self,
 769        package_name: String,
 770    ) -> wasmtime::Result<Result<String, String>> {
 771        self.host
 772            .node_runtime
 773            .npm_package_latest_version(&package_name)
 774            .await
 775            .map(|v| v.to_string())
 776            .to_wasmtime_result()
 777    }
 778
 779    async fn npm_package_installed_version(
 780        &mut self,
 781        package_name: String,
 782    ) -> wasmtime::Result<Result<Option<String>, String>> {
 783        self.host
 784            .node_runtime
 785            .npm_package_installed_version(&self.work_dir(), &package_name)
 786            .await
 787            .map(|option| option.map(|version| version.to_string()))
 788            .to_wasmtime_result()
 789    }
 790
 791    async fn npm_install_package(
 792        &mut self,
 793        package_name: String,
 794        version: String,
 795    ) -> wasmtime::Result<Result<(), String>> {
 796        self.capability_granter
 797            .grant_npm_install_package(&package_name)?;
 798
 799        self.host
 800            .node_runtime
 801            .npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
 802            .await
 803            .to_wasmtime_result()
 804    }
 805}
 806
 807#[async_trait]
 808impl lsp::Host for WasmState {}
 809
 810impl From<::http_client::github::GithubRelease> for github::GithubRelease {
 811    fn from(value: ::http_client::github::GithubRelease) -> Self {
 812        Self {
 813            version: value.tag_name,
 814            assets: value.assets.into_iter().map(Into::into).collect(),
 815        }
 816    }
 817}
 818
 819impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
 820    fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
 821        Self {
 822            name: value.name,
 823            download_url: value.browser_download_url,
 824        }
 825    }
 826}
 827
 828impl github::Host for WasmState {
 829    async fn latest_github_release(
 830        &mut self,
 831        repo: String,
 832        options: github::GithubReleaseOptions,
 833    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
 834        maybe!(async {
 835            let release = ::http_client::github::latest_github_release(
 836                &repo,
 837                options.require_assets,
 838                options.pre_release,
 839                self.host.http_client.clone(),
 840            )
 841            .await?;
 842            Ok(release.into())
 843        })
 844        .await
 845        .to_wasmtime_result()
 846    }
 847
 848    async fn github_release_by_tag_name(
 849        &mut self,
 850        repo: String,
 851        tag: String,
 852    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
 853        maybe!(async {
 854            let release = ::http_client::github::get_release_by_tag_name(
 855                &repo,
 856                &tag,
 857                self.host.http_client.clone(),
 858            )
 859            .await?;
 860            Ok(release.into())
 861        })
 862        .await
 863        .to_wasmtime_result()
 864    }
 865}
 866
 867impl platform::Host for WasmState {
 868    async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
 869        Ok((
 870            match env::consts::OS {
 871                "macos" => platform::Os::Mac,
 872                "linux" => platform::Os::Linux,
 873                "windows" => platform::Os::Windows,
 874                _ => panic!("unsupported os"),
 875            },
 876            match env::consts::ARCH {
 877                "aarch64" => platform::Architecture::Aarch64,
 878                "x86" => platform::Architecture::X86,
 879                "x86_64" => platform::Architecture::X8664,
 880                _ => panic!("unsupported architecture"),
 881            },
 882        ))
 883    }
 884}
 885
 886impl From<std::process::Output> for process::Output {
 887    fn from(output: std::process::Output) -> Self {
 888        Self {
 889            status: output.status.code(),
 890            stdout: output.stdout,
 891            stderr: output.stderr,
 892        }
 893    }
 894}
 895
 896impl process::Host for WasmState {
 897    async fn run_command(
 898        &mut self,
 899        command: process::Command,
 900    ) -> wasmtime::Result<Result<process::Output, String>> {
 901        maybe!(async {
 902            self.capability_granter
 903                .grant_exec(&command.command, &command.args)?;
 904
 905            let output = util::command::new_smol_command(command.command.as_str())
 906                .args(&command.args)
 907                .envs(command.env)
 908                .output()
 909                .await?;
 910
 911            Ok(output.into())
 912        })
 913        .await
 914        .to_wasmtime_result()
 915    }
 916}
 917
 918#[async_trait]
 919impl slash_command::Host for WasmState {}
 920
 921#[async_trait]
 922impl context_server::Host for WasmState {}
 923
 924impl dap::Host for WasmState {
 925    async fn resolve_tcp_template(
 926        &mut self,
 927        template: TcpArgumentsTemplate,
 928    ) -> wasmtime::Result<Result<TcpArguments, String>> {
 929        maybe!(async {
 930            let (host, port, timeout) =
 931                ::dap::configure_tcp_connection(task::TcpArgumentsTemplate {
 932                    port: template.port,
 933                    host: template.host.map(Ipv4Addr::from_bits),
 934                    timeout: template.timeout,
 935                })
 936                .await?;
 937            Ok(TcpArguments {
 938                port,
 939                host: host.to_bits(),
 940                timeout,
 941            })
 942        })
 943        .await
 944        .to_wasmtime_result()
 945    }
 946}
 947
 948impl ExtensionImports for WasmState {
 949    async fn get_settings(
 950        &mut self,
 951        location: Option<self::SettingsLocation>,
 952        category: String,
 953        key: Option<String>,
 954    ) -> wasmtime::Result<Result<String, String>> {
 955        self.on_main_thread(|cx| {
 956            async move {
 957                let path = location.as_ref().and_then(|location| {
 958                    RelPath::new(Path::new(&location.path), PathStyle::Posix).ok()
 959                });
 960                let location = path
 961                    .as_ref()
 962                    .zip(location.as_ref())
 963                    .map(|(path, location)| ::settings::SettingsLocation {
 964                        worktree_id: WorktreeId::from_proto(location.worktree_id),
 965                        path,
 966                    });
 967
 968                cx.update(|cx| match category.as_str() {
 969                    "language" => {
 970                        let key = key.map(|k| LanguageName::new(&k));
 971                        let settings = AllLanguageSettings::get(location, cx).language(
 972                            location,
 973                            key.as_ref(),
 974                            cx,
 975                        );
 976                        Ok(serde_json::to_string(&settings::LanguageSettings {
 977                            tab_size: settings.tab_size,
 978                        })?)
 979                    }
 980                    "lsp" => {
 981                        let settings = key
 982                            .and_then(|key| {
 983                                ProjectSettings::get(location, cx)
 984                                    .lsp
 985                                    .get(&::lsp::LanguageServerName::from_proto(key))
 986                            })
 987                            .cloned()
 988                            .unwrap_or_default();
 989                        Ok(serde_json::to_string(&settings::LspSettings {
 990                            binary: settings.binary.map(|binary| settings::CommandSettings {
 991                                path: binary.path,
 992                                arguments: binary.arguments,
 993                                env: binary.env.map(|env| env.into_iter().collect()),
 994                            }),
 995                            settings: settings.settings,
 996                            initialization_options: settings.initialization_options,
 997                        })?)
 998                    }
 999                    "context_servers" => {
1000                        let settings = key
1001                            .and_then(|key| {
1002                                ProjectSettings::get(location, cx)
1003                                    .context_servers
1004                                    .get(key.as_str())
1005                            })
1006                            .cloned()
1007                            .unwrap_or_else(|| {
1008                                project::project_settings::ContextServerSettings::default_extension(
1009                                )
1010                            });
1011
1012                        match settings {
1013                            project::project_settings::ContextServerSettings::Stdio {
1014                                enabled: _,
1015                                command,
1016                            } => Ok(serde_json::to_string(&settings::ContextServerSettings {
1017                                command: Some(settings::CommandSettings {
1018                                    path: command.path.to_str().map(|path| path.to_string()),
1019                                    arguments: Some(command.args),
1020                                    env: command.env.map(|env| env.into_iter().collect()),
1021                                }),
1022                                settings: None,
1023                            })?),
1024                            project::project_settings::ContextServerSettings::Extension {
1025                                enabled: _,
1026                                settings,
1027                            } => Ok(serde_json::to_string(&settings::ContextServerSettings {
1028                                command: None,
1029                                settings: Some(settings),
1030                            })?),
1031                            project::project_settings::ContextServerSettings::Http { .. } => {
1032                                bail!("remote context server settings not supported in 0.6.0")
1033                            }
1034                        }
1035                    }
1036                    _ => {
1037                        bail!("Unknown settings category: {}", category);
1038                    }
1039                })
1040            }
1041            .boxed_local()
1042        })
1043        .await?
1044        .to_wasmtime_result()
1045    }
1046
1047    async fn set_language_server_installation_status(
1048        &mut self,
1049        server_name: String,
1050        status: LanguageServerInstallationStatus,
1051    ) -> wasmtime::Result<()> {
1052        let status = match status {
1053            LanguageServerInstallationStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
1054            LanguageServerInstallationStatus::Downloading => BinaryStatus::Downloading,
1055            LanguageServerInstallationStatus::None => BinaryStatus::None,
1056            LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
1057        };
1058
1059        self.host
1060            .proxy
1061            .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
1062
1063        Ok(())
1064    }
1065
1066    async fn download_file(
1067        &mut self,
1068        url: String,
1069        path: String,
1070        file_type: DownloadedFileType,
1071    ) -> wasmtime::Result<Result<(), String>> {
1072        maybe!(async {
1073            let parsed_url = Url::parse(&url)?;
1074            self.capability_granter.grant_download_file(&parsed_url)?;
1075
1076            let path = PathBuf::from(path);
1077            let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
1078
1079            self.host.fs.create_dir(&extension_work_dir).await?;
1080
1081            let destination_path = self
1082                .host
1083                .writeable_path_from_extension(&self.manifest.id, &path)?;
1084
1085            let mut response = self
1086                .host
1087                .http_client
1088                .get(&url, Default::default(), true)
1089                .await
1090                .context("downloading release")?;
1091
1092            anyhow::ensure!(
1093                response.status().is_success(),
1094                "download failed with status {}",
1095                response.status()
1096            );
1097            let body = BufReader::new(response.body_mut());
1098
1099            match file_type {
1100                DownloadedFileType::Uncompressed => {
1101                    futures::pin_mut!(body);
1102                    self.host
1103                        .fs
1104                        .create_file_with(&destination_path, body)
1105                        .await?;
1106                }
1107                DownloadedFileType::Gzip => {
1108                    let body = GzipDecoder::new(body);
1109                    futures::pin_mut!(body);
1110                    self.host
1111                        .fs
1112                        .create_file_with(&destination_path, body)
1113                        .await?;
1114                }
1115                DownloadedFileType::GzipTar => {
1116                    let body = GzipDecoder::new(body);
1117                    futures::pin_mut!(body);
1118                    self.host
1119                        .fs
1120                        .extract_tar_file(&destination_path, Archive::new(body))
1121                        .await?;
1122                }
1123                DownloadedFileType::Zip => {
1124                    futures::pin_mut!(body);
1125                    extract_zip(&destination_path, body)
1126                        .await
1127                        .with_context(|| format!("unzipping {path:?} archive"))?;
1128                }
1129            }
1130
1131            Ok(())
1132        })
1133        .await
1134        .to_wasmtime_result()
1135    }
1136
1137    async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
1138        let path = self
1139            .host
1140            .writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
1141
1142        make_file_executable(&path)
1143            .await
1144            .with_context(|| format!("setting permissions for path {path:?}"))
1145            .to_wasmtime_result()
1146    }
1147}
1148
1149impl llm_provider::Host for WasmState {
1150    async fn get_credential(&mut self, provider_id: String) -> wasmtime::Result<Option<String>> {
1151        let extension_id = self.manifest.id.clone();
1152        let is_legacy_extension = crate::LEGACY_LLM_EXTENSION_IDS.contains(&extension_id.as_ref());
1153
1154        // Check if this provider has env vars configured and if the user has allowed any of them
1155        let env_vars = self
1156            .manifest
1157            .language_model_providers
1158            .get(&Arc::<str>::from(provider_id.as_str()))
1159            .and_then(|entry| entry.auth.as_ref())
1160            .and_then(|auth| auth.env_vars.clone());
1161
1162        if let Some(env_vars) = env_vars {
1163            let full_provider_id = format!("{}:{}", extension_id, provider_id);
1164
1165            // Check each env var to see if it's allowed and set
1166            for env_var_name in &env_vars {
1167                let settings_key: Arc<str> =
1168                    format!("{}:{}", full_provider_id, env_var_name).into();
1169
1170                // For legacy extensions, auto-allow if env var is set
1171                let env_var_is_set = env::var(env_var_name)
1172                    .map(|v| !v.is_empty())
1173                    .unwrap_or(false);
1174
1175                let is_allowed = self
1176                    .on_main_thread({
1177                        let settings_key = settings_key.clone();
1178                        move |cx| {
1179                            async move {
1180                                cx.update(|cx| {
1181                                    crate::extension_settings::ExtensionSettings::get_global(cx)
1182                                        .allowed_env_var_providers
1183                                        .contains(&settings_key)
1184                                })
1185                            }
1186                            .boxed_local()
1187                        }
1188                    })
1189                    .await
1190                    .unwrap_or(false);
1191
1192                if is_allowed || (is_legacy_extension && env_var_is_set) {
1193                    if let Ok(value) = env::var(env_var_name) {
1194                        if !value.is_empty() {
1195                            return Ok(Some(value));
1196                        }
1197                    }
1198                }
1199            }
1200        }
1201
1202        // Fall back to credential store
1203        let credential_key = format!("extension-llm-{}:{}", extension_id, provider_id);
1204
1205        self.on_main_thread(move |cx| {
1206            async move {
1207                let credentials_provider = cx.update(|cx| <dyn CredentialsProvider>::global(cx))?;
1208                let result = credentials_provider
1209                    .read_credentials(&credential_key, cx)
1210                    .await
1211                    .ok()
1212                    .flatten();
1213                Ok(result.map(|(_, password)| String::from_utf8_lossy(&password).to_string()))
1214            }
1215            .boxed_local()
1216        })
1217        .await
1218    }
1219
1220    async fn store_credential(
1221        &mut self,
1222        provider_id: String,
1223        value: String,
1224    ) -> wasmtime::Result<Result<(), String>> {
1225        let extension_id = self.manifest.id.clone();
1226        let credential_key = format!("extension-llm-{}:{}", extension_id, provider_id);
1227
1228        self.on_main_thread(move |cx| {
1229            async move {
1230                let credentials_provider = cx.update(|cx| <dyn CredentialsProvider>::global(cx))?;
1231                credentials_provider
1232                    .write_credentials(&credential_key, "api_key", value.as_bytes(), cx)
1233                    .await
1234                    .map_err(|e| anyhow::anyhow!("{}", e))
1235            }
1236            .boxed_local()
1237        })
1238        .await
1239        .to_wasmtime_result()
1240    }
1241
1242    async fn delete_credential(
1243        &mut self,
1244        provider_id: String,
1245    ) -> wasmtime::Result<Result<(), String>> {
1246        let extension_id = self.manifest.id.clone();
1247        let credential_key = format!("extension-llm-{}:{}", extension_id, provider_id);
1248
1249        self.on_main_thread(move |cx| {
1250            async move {
1251                let credentials_provider = cx.update(|cx| <dyn CredentialsProvider>::global(cx))?;
1252                credentials_provider
1253                    .delete_credentials(&credential_key, cx)
1254                    .await
1255                    .map_err(|e| anyhow::anyhow!("{}", e))
1256            }
1257            .boxed_local()
1258        })
1259        .await
1260        .to_wasmtime_result()
1261    }
1262
1263    async fn get_env_var(&mut self, name: String) -> wasmtime::Result<Option<String>> {
1264        let extension_id = self.manifest.id.clone();
1265
1266        // Find which provider (if any) declares this env var in its auth config
1267        let mut allowed_provider_id: Option<Arc<str>> = None;
1268        for (provider_id, provider_entry) in &self.manifest.language_model_providers {
1269            if let Some(auth_config) = &provider_entry.auth {
1270                if let Some(env_vars) = &auth_config.env_vars {
1271                    if env_vars.iter().any(|v| v == &name) {
1272                        allowed_provider_id = Some(provider_id.clone());
1273                        break;
1274                    }
1275                }
1276            }
1277        }
1278
1279        // If no provider declares this env var, deny access
1280        let Some(provider_id) = allowed_provider_id else {
1281            log::warn!(
1282                "Extension {} attempted to read env var {} which is not declared in any provider auth config",
1283                extension_id,
1284                name
1285            );
1286            return Ok(None);
1287        };
1288
1289        // Check if the user has allowed this specific env var
1290        let settings_key: Arc<str> = format!("{}:{}:{}", extension_id, provider_id, name).into();
1291        let is_legacy_extension = crate::LEGACY_LLM_EXTENSION_IDS.contains(&extension_id.as_ref());
1292
1293        // For legacy extensions, auto-allow if env var is set
1294        let env_var_is_set = env::var(&name).map(|v| !v.is_empty()).unwrap_or(false);
1295
1296        let is_allowed = self
1297            .on_main_thread({
1298                let settings_key = settings_key.clone();
1299                move |cx| {
1300                    async move {
1301                        cx.update(|cx| {
1302                            crate::extension_settings::ExtensionSettings::get_global(cx)
1303                                .allowed_env_var_providers
1304                                .contains(&settings_key)
1305                        })
1306                    }
1307                    .boxed_local()
1308                }
1309            })
1310            .await
1311            .unwrap_or(false);
1312
1313        if !is_allowed && !(is_legacy_extension && env_var_is_set) {
1314            log::debug!(
1315                "Extension {} provider {} is not allowed to read env var {}",
1316                extension_id,
1317                provider_id,
1318                name
1319            );
1320            return Ok(None);
1321        }
1322
1323        Ok(env::var(&name).ok())
1324    }
1325
1326    async fn oauth_start_web_auth(
1327        &mut self,
1328        config: llm_provider::OauthWebAuthConfig,
1329    ) -> wasmtime::Result<Result<llm_provider::OauthWebAuthResult, String>> {
1330        let auth_url = config.auth_url;
1331        let callback_path = config.callback_path;
1332        let timeout_secs = config.timeout_secs.unwrap_or(300);
1333
1334        self.on_main_thread(move |cx| {
1335            async move {
1336                // Bind to port 0 to let the OS assign an available port, then substitute
1337                // it into the auth URL's {port} placeholder for the OAuth callback.
1338                let listener = TcpListener::bind("127.0.0.1:0")
1339                    .await
1340                    .map_err(|e| anyhow::anyhow!("Failed to bind localhost server: {}", e))?;
1341                let port = listener
1342                    .local_addr()
1343                    .map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?
1344                    .port();
1345
1346                let auth_url_with_port = auth_url.replace("{port}", &port.to_string());
1347                cx.update(|cx| {
1348                    cx.open_url(&auth_url_with_port);
1349                })?;
1350
1351                let accept_future = async {
1352                    let (mut stream, _) = listener
1353                        .accept()
1354                        .await
1355                        .map_err(|e| anyhow::anyhow!("Failed to accept connection: {}", e))?;
1356
1357                    let mut request_line = String::new();
1358                    {
1359                        let mut reader = smol::io::BufReader::new(&mut stream);
1360                        smol::io::AsyncBufReadExt::read_line(&mut reader, &mut request_line)
1361                            .await
1362                            .map_err(|e| anyhow::anyhow!("Failed to read request: {}", e))?;
1363                    }
1364
1365                    let path = request_line
1366                        .split_whitespace()
1367                        .nth(1)
1368                        .ok_or_else(|| anyhow::anyhow!("Malformed HTTP request"))?;
1369
1370                    let callback_url = if path.starts_with(&callback_path)
1371                        || path.starts_with(&format!("/{}", callback_path.trim_start_matches('/')))
1372                    {
1373                        format!("http://localhost:{}{}", port, path)
1374                    } else {
1375                        return Err(anyhow::anyhow!("Unexpected callback path: {}", path));
1376                    };
1377
1378                    let response = format!(
1379                        "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n{}",
1380                        include_str!("../oauth_callback_response.html")
1381                    );
1382
1383                    smol::io::AsyncWriteExt::write_all(&mut stream, response.as_bytes())
1384                        .await
1385                        .ok();
1386                    smol::io::AsyncWriteExt::flush(&mut stream).await.ok();
1387
1388                    Ok(callback_url)
1389                };
1390
1391                let timeout_duration = Duration::from_secs(timeout_secs as u64);
1392                let callback_url = smol::future::or(accept_future, async {
1393                    smol::Timer::after(timeout_duration).await;
1394                    Err(anyhow::anyhow!(
1395                        "OAuth callback timed out after {} seconds",
1396                        timeout_secs
1397                    ))
1398                })
1399                .await?;
1400
1401                Ok(llm_provider::OauthWebAuthResult {
1402                    callback_url,
1403                    port: port as u32,
1404                })
1405            }
1406            .boxed_local()
1407        })
1408        .await
1409        .to_wasmtime_result()
1410    }
1411
1412    async fn oauth_send_http_request(
1413        &mut self,
1414        request: http_client::HttpRequest,
1415    ) -> wasmtime::Result<Result<http_client::HttpResponseWithStatus, String>> {
1416        maybe!(async {
1417            let request = convert_request(&request)?;
1418            let mut response = self.host.http_client.send(request).await?;
1419            convert_response_with_status(&mut response).await
1420        })
1421        .await
1422        .to_wasmtime_result()
1423    }
1424
1425    async fn oauth_open_browser(&mut self, url: String) -> wasmtime::Result<Result<(), String>> {
1426        self.on_main_thread(move |cx| {
1427            async move {
1428                cx.update(|cx| {
1429                    cx.open_url(&url);
1430                })?;
1431                Ok(())
1432            }
1433            .boxed_local()
1434        })
1435        .await
1436        .to_wasmtime_result()
1437    }
1438
1439    async fn get_provider_settings(
1440        &mut self,
1441        provider_id: String,
1442    ) -> wasmtime::Result<Option<llm_provider::ProviderSettings>> {
1443        let extension_id = self.manifest.id.clone();
1444
1445        let result = self
1446            .on_main_thread(move |cx| {
1447                async move {
1448                    cx.update(|cx| {
1449                        let settings_store = cx.global::<SettingsStore>();
1450                        let user_settings = settings_store.raw_user_settings();
1451                        let language_models =
1452                            user_settings.and_then(|s| s.content.language_models.as_ref());
1453
1454                        // Map provider IDs to their settings
1455                        // The provider_id from the extension is just the provider part (e.g., "google-ai")
1456                        // We need to match this to the appropriate settings
1457                        match provider_id.as_str() {
1458                            "google-ai" => {
1459                                let google = language_models.and_then(|lm| lm.google.as_ref());
1460                                let google = google?;
1461
1462                                let api_url = google.api_url.clone().filter(|s| !s.is_empty());
1463
1464                                let available_models = google
1465                                    .available_models
1466                                    .as_ref()
1467                                    .map(|models| {
1468                                        models
1469                                            .iter()
1470                                            .map(|m| {
1471                                                let thinking_budget = match &m.mode {
1472                                                    Some(ModelMode::Thinking { budget_tokens }) => {
1473                                                        *budget_tokens
1474                                                    }
1475                                                    _ => None,
1476                                                };
1477                                                llm_provider::CustomModelConfig {
1478                                                    name: m.name.clone(),
1479                                                    display_name: m.display_name.clone(),
1480                                                    max_tokens: m.max_tokens,
1481                                                    max_output_tokens: None,
1482                                                    thinking_budget,
1483                                                }
1484                                            })
1485                                            .collect()
1486                                    })
1487                                    .unwrap_or_default();
1488
1489                                Some(llm_provider::ProviderSettings {
1490                                    api_url,
1491                                    available_models,
1492                                })
1493                            }
1494                            _ => {
1495                                log::debug!(
1496                                    "Extension {} requested settings for unknown provider: {}",
1497                                    extension_id,
1498                                    provider_id
1499                                );
1500                                None
1501                            }
1502                        }
1503                    })
1504                    .ok()
1505                    .flatten()
1506                }
1507                .boxed_local()
1508            })
1509            .await;
1510
1511        Ok(result)
1512    }
1513}