since_v0_7_0.rs

   1use crate::wasm_host::wit::since_v0_7_0::{
   2    dap::{
   3        AttachRequest, BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, LaunchRequest,
   4        StartDebuggingRequestArguments, 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::{Settings, WorktreeId};
  12use anyhow::{Context as _, Result, bail};
  13use async_compression::futures::bufread::GzipDecoder;
  14use async_tar::Archive;
  15use async_trait::async_trait;
  16use extension::{
  17    ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
  18};
  19use futures::{AsyncReadExt, lock::Mutex};
  20use futures::{FutureExt as _, io::BufReader};
  21use gpui::{BackgroundExecutor, SharedString};
  22use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings};
  23use project::project_settings::ProjectSettings;
  24use semver::Version;
  25use std::{
  26    env,
  27    net::Ipv4Addr,
  28    path::{Path, PathBuf},
  29    str::FromStr,
  30    sync::{Arc, OnceLock},
  31};
  32use task::{SpawnInTerminal, ZedDebugConfig};
  33use url::Url;
  34use util::{
  35    archive::extract_zip, fs::make_file_executable, maybe, paths::PathStyle, rel_path::RelPath,
  36};
  37use wasmtime::component::{Linker, Resource};
  38
  39pub const MIN_VERSION: Version = Version::new(0, 7, 0);
  40pub const MAX_VERSION: Version = Version::new(0, 8, 0);
  41
  42wasmtime::component::bindgen!({
  43    async: true,
  44    trappable_imports: true,
  45    path: "../extension_api/wit/since_v0.7.0",
  46    with: {
  47         "worktree": ExtensionWorktree,
  48         "project": ExtensionProject,
  49         "key-value-store": ExtensionKeyValueStore,
  50         "zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream,
  51    },
  52});
  53
  54// This is the latest version, so we pub use to make types available to parent module.
  55// Note: The parent wit.rs module re-exports specific types from here as the "latest" types.
  56pub use self::zed::extension::*;
  57
  58mod settings {
  59    #![allow(dead_code)]
  60    include!(concat!(env!("OUT_DIR"), "/since_v0.7.0/settings.rs"));
  61}
  62
  63pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
  64pub type ExtensionProject = Arc<dyn ProjectDelegate>;
  65pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
  66pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
  67
  68pub fn linker(executor: &BackgroundExecutor) -> &'static Linker<WasmState> {
  69    static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
  70    LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker))
  71}
  72
  73impl From<Range> for std::ops::Range<usize> {
  74    fn from(range: Range) -> Self {
  75        let start = range.start as usize;
  76        let end = range.end as usize;
  77        start..end
  78    }
  79}
  80
  81impl From<Command> for extension::Command {
  82    fn from(value: Command) -> Self {
  83        Self {
  84            command: value.command.into(),
  85            args: value.args,
  86            env: value.env,
  87        }
  88    }
  89}
  90
  91impl From<StartDebuggingRequestArgumentsRequest>
  92    for extension::StartDebuggingRequestArgumentsRequest
  93{
  94    fn from(value: StartDebuggingRequestArgumentsRequest) -> Self {
  95        match value {
  96            StartDebuggingRequestArgumentsRequest::Launch => Self::Launch,
  97            StartDebuggingRequestArgumentsRequest::Attach => Self::Attach,
  98        }
  99    }
 100}
 101impl TryFrom<StartDebuggingRequestArguments> for extension::StartDebuggingRequestArguments {
 102    type Error = anyhow::Error;
 103
 104    fn try_from(value: StartDebuggingRequestArguments) -> Result<Self, Self::Error> {
 105        Ok(Self {
 106            configuration: serde_json::from_str(&value.configuration)?,
 107            request: value.request.into(),
 108        })
 109    }
 110}
 111impl From<TcpArguments> for extension::TcpArguments {
 112    fn from(value: TcpArguments) -> Self {
 113        Self {
 114            host: value.host.into(),
 115            port: value.port,
 116            timeout: value.timeout,
 117        }
 118    }
 119}
 120
 121impl From<extension::TcpArgumentsTemplate> for TcpArgumentsTemplate {
 122    fn from(value: extension::TcpArgumentsTemplate) -> Self {
 123        Self {
 124            host: value.host.map(Ipv4Addr::to_bits),
 125            port: value.port,
 126            timeout: value.timeout,
 127        }
 128    }
 129}
 130
 131impl From<TcpArgumentsTemplate> for extension::TcpArgumentsTemplate {
 132    fn from(value: TcpArgumentsTemplate) -> Self {
 133        Self {
 134            host: value.host.map(Ipv4Addr::from_bits),
 135            port: value.port,
 136            timeout: value.timeout,
 137        }
 138    }
 139}
 140
 141impl TryFrom<extension::DebugTaskDefinition> for DebugTaskDefinition {
 142    type Error = anyhow::Error;
 143    fn try_from(value: extension::DebugTaskDefinition) -> Result<Self, Self::Error> {
 144        Ok(Self {
 145            label: value.label.to_string(),
 146            adapter: value.adapter.to_string(),
 147            config: value.config.to_string(),
 148            tcp_connection: value.tcp_connection.map(Into::into),
 149        })
 150    }
 151}
 152
 153impl From<task::DebugRequest> for DebugRequest {
 154    fn from(value: task::DebugRequest) -> Self {
 155        match value {
 156            task::DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()),
 157            task::DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()),
 158        }
 159    }
 160}
 161
 162impl From<DebugRequest> for task::DebugRequest {
 163    fn from(value: DebugRequest) -> Self {
 164        match value {
 165            DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()),
 166            DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()),
 167        }
 168    }
 169}
 170
 171impl From<task::LaunchRequest> for LaunchRequest {
 172    fn from(value: task::LaunchRequest) -> Self {
 173        Self {
 174            program: value.program,
 175            cwd: value.cwd.map(|p| p.to_string_lossy().into_owned()),
 176            args: value.args,
 177            envs: value.env.into_iter().collect(),
 178        }
 179    }
 180}
 181
 182impl From<task::AttachRequest> for AttachRequest {
 183    fn from(value: task::AttachRequest) -> Self {
 184        Self {
 185            process_id: value.process_id,
 186        }
 187    }
 188}
 189
 190impl From<LaunchRequest> for task::LaunchRequest {
 191    fn from(value: LaunchRequest) -> Self {
 192        Self {
 193            program: value.program,
 194            cwd: value.cwd.map(|p| p.into()),
 195            args: value.args,
 196            env: value.envs.into_iter().collect(),
 197        }
 198    }
 199}
 200impl From<AttachRequest> for task::AttachRequest {
 201    fn from(value: AttachRequest) -> Self {
 202        Self {
 203            process_id: value.process_id,
 204        }
 205    }
 206}
 207
 208impl From<ZedDebugConfig> for DebugConfig {
 209    fn from(value: ZedDebugConfig) -> Self {
 210        Self {
 211            label: value.label.into(),
 212            adapter: value.adapter.into(),
 213            request: value.request.into(),
 214            stop_on_entry: value.stop_on_entry,
 215        }
 216    }
 217}
 218impl TryFrom<DebugAdapterBinary> for extension::DebugAdapterBinary {
 219    type Error = anyhow::Error;
 220    fn try_from(value: DebugAdapterBinary) -> Result<Self, Self::Error> {
 221        Ok(Self {
 222            command: value.command,
 223            arguments: value.arguments,
 224            envs: value.envs.into_iter().collect(),
 225            cwd: value.cwd.map(|s| s.into()),
 226            connection: value.connection.map(Into::into),
 227            request_args: value.request_args.try_into()?,
 228        })
 229    }
 230}
 231
 232impl From<BuildTaskDefinition> for extension::BuildTaskDefinition {
 233    fn from(value: BuildTaskDefinition) -> Self {
 234        match value {
 235            BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
 236            BuildTaskDefinition::Template(build_task_template) => Self::Template {
 237                task_template: build_task_template.template.into(),
 238                locator_name: build_task_template.locator_name.map(SharedString::from),
 239            },
 240        }
 241    }
 242}
 243
 244impl From<extension::BuildTaskDefinition> for BuildTaskDefinition {
 245    fn from(value: extension::BuildTaskDefinition) -> Self {
 246        match value {
 247            extension::BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
 248            extension::BuildTaskDefinition::Template {
 249                task_template,
 250                locator_name,
 251            } => Self::Template(BuildTaskDefinitionTemplatePayload {
 252                template: task_template.into(),
 253                locator_name: locator_name.map(String::from),
 254            }),
 255        }
 256    }
 257}
 258impl From<BuildTaskTemplate> for extension::BuildTaskTemplate {
 259    fn from(value: BuildTaskTemplate) -> Self {
 260        Self {
 261            label: value.label,
 262            command: value.command,
 263            args: value.args,
 264            env: value.env.into_iter().collect(),
 265            cwd: value.cwd,
 266            ..Default::default()
 267        }
 268    }
 269}
 270impl From<extension::BuildTaskTemplate> for BuildTaskTemplate {
 271    fn from(value: extension::BuildTaskTemplate) -> Self {
 272        Self {
 273            label: value.label,
 274            command: value.command,
 275            args: value.args,
 276            env: value.env.into_iter().collect(),
 277            cwd: value.cwd,
 278        }
 279    }
 280}
 281
 282impl TryFrom<DebugScenario> for extension::DebugScenario {
 283    type Error = anyhow::Error;
 284
 285    fn try_from(value: DebugScenario) -> std::result::Result<Self, Self::Error> {
 286        Ok(Self {
 287            adapter: value.adapter.into(),
 288            label: value.label.into(),
 289            build: value.build.map(Into::into),
 290            config: serde_json::Value::from_str(&value.config)?,
 291            tcp_connection: value.tcp_connection.map(Into::into),
 292        })
 293    }
 294}
 295
 296impl From<extension::DebugScenario> for DebugScenario {
 297    fn from(value: extension::DebugScenario) -> Self {
 298        Self {
 299            adapter: value.adapter.into(),
 300            label: value.label.into(),
 301            build: value.build.map(Into::into),
 302            config: value.config.to_string(),
 303            tcp_connection: value.tcp_connection.map(Into::into),
 304        }
 305    }
 306}
 307
 308impl TryFrom<SpawnInTerminal> for ResolvedTask {
 309    type Error = anyhow::Error;
 310
 311    fn try_from(value: SpawnInTerminal) -> Result<Self, Self::Error> {
 312        Ok(Self {
 313            label: value.label,
 314            command: value.command.context("missing command")?,
 315            args: value.args,
 316            env: value.env.into_iter().collect(),
 317            cwd: value.cwd.map(|s| {
 318                let s = s.to_string_lossy();
 319                if cfg!(target_os = "windows") {
 320                    s.replace('\\', "/")
 321                } else {
 322                    s.into_owned()
 323                }
 324            }),
 325        })
 326    }
 327}
 328
 329impl From<CodeLabel> for extension::CodeLabel {
 330    fn from(value: CodeLabel) -> Self {
 331        Self {
 332            code: value.code,
 333            spans: value.spans.into_iter().map(Into::into).collect(),
 334            filter_range: value.filter_range.into(),
 335        }
 336    }
 337}
 338
 339impl From<CodeLabelSpan> for extension::CodeLabelSpan {
 340    fn from(value: CodeLabelSpan) -> Self {
 341        match value {
 342            CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
 343            CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
 344        }
 345    }
 346}
 347
 348impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
 349    fn from(value: CodeLabelSpanLiteral) -> Self {
 350        Self {
 351            text: value.text,
 352            highlight_name: value.highlight_name,
 353        }
 354    }
 355}
 356
 357impl From<extension::Completion> for Completion {
 358    fn from(value: extension::Completion) -> Self {
 359        Self {
 360            label: value.label,
 361            label_details: value.label_details.map(Into::into),
 362            detail: value.detail,
 363            kind: value.kind.map(Into::into),
 364            insert_text_format: value.insert_text_format.map(Into::into),
 365        }
 366    }
 367}
 368
 369impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
 370    fn from(value: extension::CompletionLabelDetails) -> Self {
 371        Self {
 372            detail: value.detail,
 373            description: value.description,
 374        }
 375    }
 376}
 377
 378impl From<extension::CompletionKind> for CompletionKind {
 379    fn from(value: extension::CompletionKind) -> Self {
 380        match value {
 381            extension::CompletionKind::Text => Self::Text,
 382            extension::CompletionKind::Method => Self::Method,
 383            extension::CompletionKind::Function => Self::Function,
 384            extension::CompletionKind::Constructor => Self::Constructor,
 385            extension::CompletionKind::Field => Self::Field,
 386            extension::CompletionKind::Variable => Self::Variable,
 387            extension::CompletionKind::Class => Self::Class,
 388            extension::CompletionKind::Interface => Self::Interface,
 389            extension::CompletionKind::Module => Self::Module,
 390            extension::CompletionKind::Property => Self::Property,
 391            extension::CompletionKind::Unit => Self::Unit,
 392            extension::CompletionKind::Value => Self::Value,
 393            extension::CompletionKind::Enum => Self::Enum,
 394            extension::CompletionKind::Keyword => Self::Keyword,
 395            extension::CompletionKind::Snippet => Self::Snippet,
 396            extension::CompletionKind::Color => Self::Color,
 397            extension::CompletionKind::File => Self::File,
 398            extension::CompletionKind::Reference => Self::Reference,
 399            extension::CompletionKind::Folder => Self::Folder,
 400            extension::CompletionKind::EnumMember => Self::EnumMember,
 401            extension::CompletionKind::Constant => Self::Constant,
 402            extension::CompletionKind::Struct => Self::Struct,
 403            extension::CompletionKind::Event => Self::Event,
 404            extension::CompletionKind::Operator => Self::Operator,
 405            extension::CompletionKind::TypeParameter => Self::TypeParameter,
 406            extension::CompletionKind::Other(value) => Self::Other(value),
 407        }
 408    }
 409}
 410
 411impl From<extension::InsertTextFormat> for InsertTextFormat {
 412    fn from(value: extension::InsertTextFormat) -> Self {
 413        match value {
 414            extension::InsertTextFormat::PlainText => Self::PlainText,
 415            extension::InsertTextFormat::Snippet => Self::Snippet,
 416            extension::InsertTextFormat::Other(value) => Self::Other(value),
 417        }
 418    }
 419}
 420
 421impl From<extension::Symbol> for Symbol {
 422    fn from(value: extension::Symbol) -> Self {
 423        Self {
 424            kind: value.kind.into(),
 425            name: value.name,
 426        }
 427    }
 428}
 429
 430impl From<extension::SymbolKind> for SymbolKind {
 431    fn from(value: extension::SymbolKind) -> Self {
 432        match value {
 433            extension::SymbolKind::File => Self::File,
 434            extension::SymbolKind::Module => Self::Module,
 435            extension::SymbolKind::Namespace => Self::Namespace,
 436            extension::SymbolKind::Package => Self::Package,
 437            extension::SymbolKind::Class => Self::Class,
 438            extension::SymbolKind::Method => Self::Method,
 439            extension::SymbolKind::Property => Self::Property,
 440            extension::SymbolKind::Field => Self::Field,
 441            extension::SymbolKind::Constructor => Self::Constructor,
 442            extension::SymbolKind::Enum => Self::Enum,
 443            extension::SymbolKind::Interface => Self::Interface,
 444            extension::SymbolKind::Function => Self::Function,
 445            extension::SymbolKind::Variable => Self::Variable,
 446            extension::SymbolKind::Constant => Self::Constant,
 447            extension::SymbolKind::String => Self::String,
 448            extension::SymbolKind::Number => Self::Number,
 449            extension::SymbolKind::Boolean => Self::Boolean,
 450            extension::SymbolKind::Array => Self::Array,
 451            extension::SymbolKind::Object => Self::Object,
 452            extension::SymbolKind::Key => Self::Key,
 453            extension::SymbolKind::Null => Self::Null,
 454            extension::SymbolKind::EnumMember => Self::EnumMember,
 455            extension::SymbolKind::Struct => Self::Struct,
 456            extension::SymbolKind::Event => Self::Event,
 457            extension::SymbolKind::Operator => Self::Operator,
 458            extension::SymbolKind::TypeParameter => Self::TypeParameter,
 459            extension::SymbolKind::Other(value) => Self::Other(value),
 460        }
 461    }
 462}
 463
 464impl From<extension::SlashCommand> for SlashCommand {
 465    fn from(value: extension::SlashCommand) -> Self {
 466        Self {
 467            name: value.name,
 468            description: value.description,
 469            tooltip_text: value.tooltip_text,
 470            requires_argument: value.requires_argument,
 471        }
 472    }
 473}
 474
 475impl From<SlashCommandOutput> for extension::SlashCommandOutput {
 476    fn from(value: SlashCommandOutput) -> Self {
 477        Self {
 478            text: value.text,
 479            sections: value.sections.into_iter().map(Into::into).collect(),
 480        }
 481    }
 482}
 483
 484impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
 485    fn from(value: SlashCommandOutputSection) -> Self {
 486        Self {
 487            range: value.range.start as usize..value.range.end as usize,
 488            label: value.label,
 489        }
 490    }
 491}
 492
 493impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
 494    fn from(value: SlashCommandArgumentCompletion) -> Self {
 495        Self {
 496            label: value.label,
 497            new_text: value.new_text,
 498            run_command: value.run_command,
 499        }
 500    }
 501}
 502
 503impl TryFrom<ContextServerConfiguration> for extension::ContextServerConfiguration {
 504    type Error = anyhow::Error;
 505
 506    fn try_from(value: ContextServerConfiguration) -> Result<Self, Self::Error> {
 507        let settings_schema: serde_json::Value = serde_json::from_str(&value.settings_schema)
 508            .context("Failed to parse settings_schema")?;
 509
 510        Ok(Self {
 511            installation_instructions: value.installation_instructions,
 512            default_settings: value.default_settings,
 513            settings_schema,
 514        })
 515    }
 516}
 517
 518impl HostKeyValueStore for WasmState {
 519    async fn insert(
 520        &mut self,
 521        kv_store: Resource<ExtensionKeyValueStore>,
 522        key: String,
 523        value: String,
 524    ) -> wasmtime::Result<Result<(), String>> {
 525        let kv_store = self.table.get(&kv_store)?;
 526        kv_store.insert(key, value).await.to_wasmtime_result()
 527    }
 528
 529    async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
 530        // We only ever hand out borrows of key-value stores.
 531        Ok(())
 532    }
 533}
 534
 535impl HostProject for WasmState {
 536    async fn worktree_ids(
 537        &mut self,
 538        project: Resource<ExtensionProject>,
 539    ) -> wasmtime::Result<Vec<u64>> {
 540        let project = self.table.get(&project)?;
 541        Ok(project.worktree_ids())
 542    }
 543
 544    async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
 545        // We only ever hand out borrows of projects.
 546        Ok(())
 547    }
 548}
 549
 550impl HostWorktree for WasmState {
 551    async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
 552        let delegate = self.table.get(&delegate)?;
 553        Ok(delegate.id())
 554    }
 555
 556    async fn root_path(
 557        &mut self,
 558        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 559    ) -> wasmtime::Result<String> {
 560        let delegate = self.table.get(&delegate)?;
 561        Ok(delegate.root_path())
 562    }
 563
 564    async fn read_text_file(
 565        &mut self,
 566        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 567        path: String,
 568    ) -> wasmtime::Result<Result<String, String>> {
 569        let delegate = self.table.get(&delegate)?;
 570        Ok(delegate
 571            .read_text_file(&RelPath::new(Path::new(&path), PathStyle::Posix)?)
 572            .await
 573            .map_err(|error| error.to_string()))
 574    }
 575
 576    async fn shell_env(
 577        &mut self,
 578        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 579    ) -> wasmtime::Result<EnvVars> {
 580        let delegate = self.table.get(&delegate)?;
 581        Ok(delegate.shell_env().await.into_iter().collect())
 582    }
 583
 584    async fn which(
 585        &mut self,
 586        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 587        binary_name: String,
 588    ) -> wasmtime::Result<Option<String>> {
 589        let delegate = self.table.get(&delegate)?;
 590        Ok(delegate.which(binary_name).await)
 591    }
 592
 593    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
 594        // We only ever hand out borrows of worktrees.
 595        Ok(())
 596    }
 597}
 598
 599impl common::Host for WasmState {}
 600
 601impl http_client::Host for WasmState {
 602    async fn fetch(
 603        &mut self,
 604        request: http_client::HttpRequest,
 605    ) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
 606        maybe!(async {
 607            let url = &request.url;
 608            let request = convert_request(&request)?;
 609            let mut response = self.host.http_client.send(request).await?;
 610
 611            if response.status().is_client_error() || response.status().is_server_error() {
 612                bail!("failed to fetch '{url}': status code {}", response.status())
 613            }
 614            convert_response(&mut response).await
 615        })
 616        .await
 617        .to_wasmtime_result()
 618    }
 619
 620    async fn fetch_stream(
 621        &mut self,
 622        request: http_client::HttpRequest,
 623    ) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
 624        let request = convert_request(&request)?;
 625        let response = self.host.http_client.send(request);
 626        maybe!(async {
 627            let response = response.await?;
 628            let stream = Arc::new(Mutex::new(response));
 629            let resource = self.table.push(stream)?;
 630            Ok(resource)
 631        })
 632        .await
 633        .to_wasmtime_result()
 634    }
 635}
 636
 637impl http_client::HostHttpResponseStream for WasmState {
 638    async fn next_chunk(
 639        &mut self,
 640        resource: Resource<ExtensionHttpResponseStream>,
 641    ) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
 642        let stream = self.table.get(&resource)?.clone();
 643        maybe!(async move {
 644            let mut response = stream.lock().await;
 645            let mut buffer = vec![0; 8192]; // 8KB buffer
 646            let bytes_read = response.body_mut().read(&mut buffer).await?;
 647            if bytes_read == 0 {
 648                Ok(None)
 649            } else {
 650                buffer.truncate(bytes_read);
 651                Ok(Some(buffer))
 652            }
 653        })
 654        .await
 655        .to_wasmtime_result()
 656    }
 657
 658    async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
 659        Ok(())
 660    }
 661}
 662
 663impl From<http_client::HttpMethod> for ::http_client::Method {
 664    fn from(value: http_client::HttpMethod) -> Self {
 665        match value {
 666            http_client::HttpMethod::Get => Self::GET,
 667            http_client::HttpMethod::Post => Self::POST,
 668            http_client::HttpMethod::Put => Self::PUT,
 669            http_client::HttpMethod::Delete => Self::DELETE,
 670            http_client::HttpMethod::Head => Self::HEAD,
 671            http_client::HttpMethod::Options => Self::OPTIONS,
 672            http_client::HttpMethod::Patch => Self::PATCH,
 673        }
 674    }
 675}
 676
 677fn convert_request(
 678    extension_request: &http_client::HttpRequest,
 679) -> anyhow::Result<::http_client::Request<AsyncBody>> {
 680    let mut request = ::http_client::Request::builder()
 681        .method(::http_client::Method::from(extension_request.method))
 682        .uri(&extension_request.url)
 683        .follow_redirects(match extension_request.redirect_policy {
 684            http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
 685            http_client::RedirectPolicy::FollowLimit(limit) => {
 686                ::http_client::RedirectPolicy::FollowLimit(limit)
 687            }
 688            http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
 689        });
 690    for (key, value) in &extension_request.headers {
 691        request = request.header(key, value);
 692    }
 693    let body = extension_request
 694        .body
 695        .clone()
 696        .map(AsyncBody::from)
 697        .unwrap_or_default();
 698    request.body(body).map_err(anyhow::Error::from)
 699}
 700
 701async fn convert_response(
 702    response: &mut ::http_client::Response<AsyncBody>,
 703) -> anyhow::Result<http_client::HttpResponse> {
 704    let mut extension_response = http_client::HttpResponse {
 705        body: Vec::new(),
 706        headers: Vec::new(),
 707    };
 708
 709    for (key, value) in response.headers() {
 710        extension_response
 711            .headers
 712            .push((key.to_string(), value.to_str().unwrap_or("").to_string()));
 713    }
 714
 715    response
 716        .body_mut()
 717        .read_to_end(&mut extension_response.body)
 718        .await?;
 719
 720    Ok(extension_response)
 721}
 722
 723impl nodejs::Host for WasmState {
 724    async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
 725        self.host
 726            .node_runtime
 727            .binary_path()
 728            .await
 729            .map(|path| path.to_string_lossy().into_owned())
 730            .to_wasmtime_result()
 731    }
 732
 733    async fn npm_package_latest_version(
 734        &mut self,
 735        package_name: String,
 736    ) -> wasmtime::Result<Result<String, String>> {
 737        self.host
 738            .node_runtime
 739            .npm_package_latest_version(&package_name)
 740            .await
 741            .to_wasmtime_result()
 742    }
 743
 744    async fn npm_package_installed_version(
 745        &mut self,
 746        package_name: String,
 747    ) -> wasmtime::Result<Result<Option<String>, String>> {
 748        self.host
 749            .node_runtime
 750            .npm_package_installed_version(&self.work_dir(), &package_name)
 751            .await
 752            .to_wasmtime_result()
 753    }
 754
 755    async fn npm_install_package(
 756        &mut self,
 757        package_name: String,
 758        version: String,
 759    ) -> wasmtime::Result<Result<(), String>> {
 760        self.capability_granter
 761            .grant_npm_install_package(&package_name)?;
 762
 763        self.host
 764            .node_runtime
 765            .npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
 766            .await
 767            .to_wasmtime_result()
 768    }
 769}
 770
 771#[async_trait]
 772impl lsp::Host for WasmState {}
 773
 774impl From<::http_client::github::GithubRelease> for github::GithubRelease {
 775    fn from(value: ::http_client::github::GithubRelease) -> Self {
 776        Self {
 777            version: value.tag_name,
 778            assets: value.assets.into_iter().map(Into::into).collect(),
 779        }
 780    }
 781}
 782
 783impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
 784    fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
 785        Self {
 786            name: value.name,
 787            download_url: value.browser_download_url,
 788        }
 789    }
 790}
 791
 792impl github::Host for WasmState {
 793    async fn latest_github_release(
 794        &mut self,
 795        repo: String,
 796        options: github::GithubReleaseOptions,
 797    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
 798        maybe!(async {
 799            let release = ::http_client::github::latest_github_release(
 800                &repo,
 801                options.require_assets,
 802                options.pre_release,
 803                self.host.http_client.clone(),
 804            )
 805            .await?;
 806            Ok(release.into())
 807        })
 808        .await
 809        .to_wasmtime_result()
 810    }
 811
 812    async fn github_release_by_tag_name(
 813        &mut self,
 814        repo: String,
 815        tag: String,
 816    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
 817        maybe!(async {
 818            let release = ::http_client::github::get_release_by_tag_name(
 819                &repo,
 820                &tag,
 821                self.host.http_client.clone(),
 822            )
 823            .await?;
 824            Ok(release.into())
 825        })
 826        .await
 827        .to_wasmtime_result()
 828    }
 829}
 830
 831impl platform::Host for WasmState {
 832    async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
 833        Ok((
 834            match env::consts::OS {
 835                "macos" => platform::Os::Mac,
 836                "linux" => platform::Os::Linux,
 837                "windows" => platform::Os::Windows,
 838                _ => panic!("unsupported os"),
 839            },
 840            match env::consts::ARCH {
 841                "aarch64" => platform::Architecture::Aarch64,
 842                "x86" => platform::Architecture::X86,
 843                "x86_64" => platform::Architecture::X8664,
 844                _ => panic!("unsupported architecture"),
 845            },
 846        ))
 847    }
 848}
 849
 850impl From<std::process::Output> for process::Output {
 851    fn from(output: std::process::Output) -> Self {
 852        Self {
 853            status: output.status.code(),
 854            stdout: output.stdout,
 855            stderr: output.stderr,
 856        }
 857    }
 858}
 859
 860impl process::Host for WasmState {
 861    async fn run_command(
 862        &mut self,
 863        command: process::Command,
 864    ) -> wasmtime::Result<Result<process::Output, String>> {
 865        maybe!(async {
 866            self.capability_granter
 867                .grant_exec(&command.command, &command.args)?;
 868
 869            let output = util::command::new_smol_command(command.command.as_str())
 870                .args(&command.args)
 871                .envs(command.env)
 872                .output()
 873                .await?;
 874
 875            Ok(output.into())
 876        })
 877        .await
 878        .to_wasmtime_result()
 879    }
 880}
 881
 882#[async_trait]
 883impl slash_command::Host for WasmState {}
 884
 885#[async_trait]
 886impl context_server::Host for WasmState {}
 887
 888impl dap::Host for WasmState {
 889    async fn resolve_tcp_template(
 890        &mut self,
 891        template: TcpArgumentsTemplate,
 892    ) -> wasmtime::Result<Result<TcpArguments, String>> {
 893        maybe!(async {
 894            let (host, port, timeout) =
 895                ::dap::configure_tcp_connection(task::TcpArgumentsTemplate {
 896                    port: template.port,
 897                    host: template.host.map(Ipv4Addr::from_bits),
 898                    timeout: template.timeout,
 899                })
 900                .await?;
 901            Ok(TcpArguments {
 902                port,
 903                host: host.to_bits(),
 904                timeout,
 905            })
 906        })
 907        .await
 908        .to_wasmtime_result()
 909    }
 910}
 911
 912impl ExtensionImports for WasmState {
 913    async fn get_settings(
 914        &mut self,
 915        location: Option<self::SettingsLocation>,
 916        category: String,
 917        key: Option<String>,
 918    ) -> wasmtime::Result<Result<String, String>> {
 919        self.on_main_thread(|cx| {
 920            async move {
 921                let path = location.as_ref().and_then(|location| {
 922                    RelPath::new(Path::new(&location.path), PathStyle::Posix).ok()
 923                });
 924                let location = path
 925                    .as_ref()
 926                    .zip(location.as_ref())
 927                    .map(|(path, location)| ::settings::SettingsLocation {
 928                        worktree_id: WorktreeId::from_proto(location.worktree_id),
 929                        path,
 930                    });
 931
 932                cx.update(|cx| match category.as_str() {
 933                    "language" => {
 934                        let key = key.map(|k| LanguageName::new(&k));
 935                        let settings = AllLanguageSettings::get(location, cx).language(
 936                            location,
 937                            key.as_ref(),
 938                            cx,
 939                        );
 940                        Ok(serde_json::to_string(&settings::LanguageSettings {
 941                            tab_size: settings.tab_size,
 942                        })?)
 943                    }
 944                    "lsp" => {
 945                        let settings = key
 946                            .and_then(|key| {
 947                                ProjectSettings::get(location, cx)
 948                                    .lsp
 949                                    .get(&::lsp::LanguageServerName::from_proto(key))
 950                            })
 951                            .cloned()
 952                            .unwrap_or_default();
 953                        Ok(serde_json::to_string(&settings::LspSettings {
 954                            binary: settings.binary.map(|binary| settings::CommandSettings {
 955                                path: binary.path,
 956                                arguments: binary.arguments,
 957                                env: binary.env.map(|env| env.into_iter().collect()),
 958                            }),
 959                            settings: settings.settings,
 960                            initialization_options: settings.initialization_options,
 961                        })?)
 962                    }
 963                    "context_servers" => {
 964                        let settings = key
 965                            .and_then(|key| {
 966                                ProjectSettings::get(location, cx)
 967                                    .context_servers
 968                                    .get(key.as_str())
 969                            })
 970                            .cloned()
 971                            .unwrap_or_else(|| {
 972                                project::project_settings::ContextServerSettings::default_extension(
 973                                )
 974                            });
 975
 976                        match settings {
 977                            project::project_settings::ContextServerSettings::Stdio {
 978                                enabled: _,
 979                                command,
 980                            } => Ok(serde_json::to_string(&settings::ContextServerSettings {
 981                                command: Some(settings::CommandSettings {
 982                                    path: command.path.to_str().map(|path| path.to_string()),
 983                                    arguments: Some(command.args),
 984                                    env: command.env.map(|env| env.into_iter().collect()),
 985                                }),
 986                                settings: None,
 987                            })?),
 988                            project::project_settings::ContextServerSettings::Extension {
 989                                enabled: _,
 990                                settings,
 991                            } => Ok(serde_json::to_string(&settings::ContextServerSettings {
 992                                command: None,
 993                                settings: Some(settings),
 994                            })?),
 995                            project::project_settings::ContextServerSettings::Http { .. } => {
 996                                bail!("remote context server settings not supported in 0.6.0")
 997                            }
 998                        }
 999                    }
1000                    _ => {
1001                        bail!("Unknown settings category: {}", category);
1002                    }
1003                })
1004            }
1005            .boxed_local()
1006        })
1007        .await?
1008        .to_wasmtime_result()
1009    }
1010
1011    async fn set_language_server_installation_status(
1012        &mut self,
1013        server_name: String,
1014        status: LanguageServerInstallationStatus,
1015    ) -> wasmtime::Result<()> {
1016        let status = match status {
1017            LanguageServerInstallationStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
1018            LanguageServerInstallationStatus::Downloading => BinaryStatus::Downloading,
1019            LanguageServerInstallationStatus::None => BinaryStatus::None,
1020            LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
1021        };
1022
1023        self.host
1024            .proxy
1025            .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
1026
1027        Ok(())
1028    }
1029
1030    async fn download_file(
1031        &mut self,
1032        url: String,
1033        path: String,
1034        file_type: DownloadedFileType,
1035    ) -> wasmtime::Result<Result<(), String>> {
1036        maybe!(async {
1037            let parsed_url = Url::parse(&url)?;
1038            self.capability_granter.grant_download_file(&parsed_url)?;
1039
1040            let path = PathBuf::from(path);
1041            let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
1042
1043            self.host.fs.create_dir(&extension_work_dir).await?;
1044
1045            let destination_path = self
1046                .host
1047                .writeable_path_from_extension(&self.manifest.id, &path)?;
1048
1049            let mut response = self
1050                .host
1051                .http_client
1052                .get(&url, Default::default(), true)
1053                .await
1054                .context("downloading release")?;
1055
1056            anyhow::ensure!(
1057                response.status().is_success(),
1058                "download failed with status {}",
1059                response.status()
1060            );
1061            let body = BufReader::new(response.body_mut());
1062
1063            match file_type {
1064                DownloadedFileType::Uncompressed => {
1065                    futures::pin_mut!(body);
1066                    self.host
1067                        .fs
1068                        .create_file_with(&destination_path, body)
1069                        .await?;
1070                }
1071                DownloadedFileType::Gzip => {
1072                    let body = GzipDecoder::new(body);
1073                    futures::pin_mut!(body);
1074                    self.host
1075                        .fs
1076                        .create_file_with(&destination_path, body)
1077                        .await?;
1078                }
1079                DownloadedFileType::GzipTar => {
1080                    let body = GzipDecoder::new(body);
1081                    futures::pin_mut!(body);
1082                    self.host
1083                        .fs
1084                        .extract_tar_file(&destination_path, Archive::new(body))
1085                        .await?;
1086                }
1087                DownloadedFileType::Zip => {
1088                    futures::pin_mut!(body);
1089                    extract_zip(&destination_path, body)
1090                        .await
1091                        .with_context(|| format!("unzipping {path:?} archive"))?;
1092                }
1093            }
1094
1095            Ok(())
1096        })
1097        .await
1098        .to_wasmtime_result()
1099    }
1100
1101    async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
1102        let path = self
1103            .host
1104            .writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
1105
1106        make_file_executable(&path)
1107            .await
1108            .with_context(|| format!("setting permissions for path {path:?}"))
1109            .to_wasmtime_result()
1110    }
1111
1112    // =========================================================================
1113    // LLM Provider Import Implementations
1114    // =========================================================================
1115
1116    async fn llm_request_credential(
1117        &mut self,
1118        _provider_id: String,
1119        _credential_type: llm_provider::CredentialType,
1120        _label: String,
1121        _placeholder: String,
1122    ) -> wasmtime::Result<Result<bool, String>> {
1123        // For now, credential requests return false (not provided)
1124        // Extensions should use llm_get_env_var to check for env vars first,
1125        // then llm_store_credential/llm_get_credential for manual storage
1126        // Full UI credential prompting will be added in a future phase
1127        Ok(Ok(false))
1128    }
1129
1130    async fn llm_get_credential(
1131        &mut self,
1132        provider_id: String,
1133    ) -> wasmtime::Result<Option<String>> {
1134        let extension_id = self.manifest.id.clone();
1135        let credential_key = format!("{}:{}", extension_id, provider_id);
1136
1137        self.on_main_thread(move |cx| {
1138            async move {
1139                let task = cx.update(|cx| cx.read_credentials(&credential_key))?;
1140                let result = task.await.ok().flatten();
1141                Ok(result.map(|(_, password)| String::from_utf8_lossy(&password).to_string()))
1142            }
1143            .boxed_local()
1144        })
1145        .await
1146    }
1147
1148    async fn llm_store_credential(
1149        &mut self,
1150        provider_id: String,
1151        value: String,
1152    ) -> wasmtime::Result<Result<(), String>> {
1153        let extension_id = self.manifest.id.clone();
1154        let credential_key = format!("{}:{}", extension_id, provider_id);
1155
1156        self.on_main_thread(move |cx| {
1157            async move {
1158                let task = cx.update(|cx| {
1159                    cx.write_credentials(&credential_key, "api_key", value.as_bytes())
1160                })?;
1161                task.await.map_err(|e| anyhow::anyhow!("{}", e))
1162            }
1163            .boxed_local()
1164        })
1165        .await
1166        .to_wasmtime_result()
1167    }
1168
1169    async fn llm_delete_credential(
1170        &mut self,
1171        provider_id: String,
1172    ) -> wasmtime::Result<Result<(), String>> {
1173        let extension_id = self.manifest.id.clone();
1174        let credential_key = format!("{}:{}", extension_id, provider_id);
1175
1176        self.on_main_thread(move |cx| {
1177            async move {
1178                let task = cx.update(|cx| cx.delete_credentials(&credential_key))?;
1179                task.await.map_err(|e| anyhow::anyhow!("{}", e))
1180            }
1181            .boxed_local()
1182        })
1183        .await
1184        .to_wasmtime_result()
1185    }
1186
1187    async fn llm_get_env_var(&mut self, name: String) -> wasmtime::Result<Option<String>> {
1188        Ok(env::var(&name).ok())
1189    }
1190}
1191
1192// =============================================================================
1193// LLM Provider Host Implementations
1194// =============================================================================
1195
1196impl llm_provider::Host for WasmState {}