since_v0_7_0.rs

   1use crate::ExtensionSettings;
   2use crate::wasm_host::wit::since_v0_7_0::{
   3    dap::{
   4        AttachRequest, BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, LaunchRequest,
   5        StartDebuggingRequestArguments, TcpArguments, TcpArgumentsTemplate,
   6    },
   7    lsp::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind},
   8    slash_command::SlashCommandOutputSection,
   9};
  10use crate::wasm_host::{WasmState, wit::ToWasmtimeResult};
  11use ::http_client::{AsyncBody, HttpRequestExt};
  12use ::settings::{Settings, WorktreeId};
  13use anyhow::{Context as _, Result, bail};
  14use async_compression::futures::bufread::GzipDecoder;
  15use async_tar::Archive;
  16use async_trait::async_trait;
  17use credentials_provider::CredentialsProvider;
  18use extension::{
  19    ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
  20};
  21use futures::{AsyncReadExt, lock::Mutex};
  22use futures::{FutureExt as _, io::BufReader};
  23use gpui::{BackgroundExecutor, SharedString};
  24use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings};
  25use project::project_settings::ProjectSettings;
  26use semver::Version;
  27use smol::net::TcpListener;
  28use std::{
  29    env,
  30    net::Ipv4Addr,
  31    path::{Path, PathBuf},
  32    str::FromStr,
  33    sync::{Arc, OnceLock},
  34    time::Duration,
  35};
  36use task::{SpawnInTerminal, ZedDebugConfig};
  37use url::Url;
  38use util::{
  39    archive::extract_zip, fs::make_file_executable, maybe, paths::PathStyle, rel_path::RelPath,
  40};
  41use wasmtime::component::{Linker, Resource};
  42
  43pub const MIN_VERSION: Version = Version::new(0, 7, 0);
  44#[allow(dead_code)]
  45pub const MAX_VERSION: Version = Version::new(0, 8, 0);
  46
  47wasmtime::component::bindgen!({
  48    async: true,
  49    trappable_imports: true,
  50    path: "../extension_api/wit/since_v0.7.0",
  51    with: {
  52         "worktree": ExtensionWorktree,
  53         "project": ExtensionProject,
  54         "key-value-store": ExtensionKeyValueStore,
  55         "zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream,
  56    },
  57});
  58
  59// This is the latest version, so we pub use to make types available to parent module.
  60// Note: The parent wit.rs module re-exports specific types from here as the "latest" types.
  61pub use self::zed::extension::*;
  62
  63mod settings {
  64    #![allow(dead_code)]
  65    include!(concat!(env!("OUT_DIR"), "/since_v0.7.0/settings.rs"));
  66}
  67
  68pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
  69pub type ExtensionProject = Arc<dyn ProjectDelegate>;
  70pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
  71pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
  72
  73pub fn linker(executor: &BackgroundExecutor) -> &'static Linker<WasmState> {
  74    static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
  75    LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker))
  76}
  77
  78impl From<Range> for std::ops::Range<usize> {
  79    fn from(range: Range) -> Self {
  80        let start = range.start as usize;
  81        let end = range.end as usize;
  82        start..end
  83    }
  84}
  85
  86impl From<Command> for extension::Command {
  87    fn from(value: Command) -> Self {
  88        Self {
  89            command: value.command.into(),
  90            args: value.args,
  91            env: value.env,
  92        }
  93    }
  94}
  95
  96impl From<StartDebuggingRequestArgumentsRequest>
  97    for extension::StartDebuggingRequestArgumentsRequest
  98{
  99    fn from(value: StartDebuggingRequestArgumentsRequest) -> Self {
 100        match value {
 101            StartDebuggingRequestArgumentsRequest::Launch => Self::Launch,
 102            StartDebuggingRequestArgumentsRequest::Attach => Self::Attach,
 103        }
 104    }
 105}
 106impl TryFrom<StartDebuggingRequestArguments> for extension::StartDebuggingRequestArguments {
 107    type Error = anyhow::Error;
 108
 109    fn try_from(value: StartDebuggingRequestArguments) -> Result<Self, Self::Error> {
 110        Ok(Self {
 111            configuration: serde_json::from_str(&value.configuration)?,
 112            request: value.request.into(),
 113        })
 114    }
 115}
 116impl From<TcpArguments> for extension::TcpArguments {
 117    fn from(value: TcpArguments) -> Self {
 118        Self {
 119            host: value.host.into(),
 120            port: value.port,
 121            timeout: value.timeout,
 122        }
 123    }
 124}
 125
 126impl From<extension::TcpArgumentsTemplate> for TcpArgumentsTemplate {
 127    fn from(value: extension::TcpArgumentsTemplate) -> Self {
 128        Self {
 129            host: value.host.map(Ipv4Addr::to_bits),
 130            port: value.port,
 131            timeout: value.timeout,
 132        }
 133    }
 134}
 135
 136impl From<TcpArgumentsTemplate> for extension::TcpArgumentsTemplate {
 137    fn from(value: TcpArgumentsTemplate) -> Self {
 138        Self {
 139            host: value.host.map(Ipv4Addr::from_bits),
 140            port: value.port,
 141            timeout: value.timeout,
 142        }
 143    }
 144}
 145
 146impl TryFrom<extension::DebugTaskDefinition> for DebugTaskDefinition {
 147    type Error = anyhow::Error;
 148    fn try_from(value: extension::DebugTaskDefinition) -> Result<Self, Self::Error> {
 149        Ok(Self {
 150            label: value.label.to_string(),
 151            adapter: value.adapter.to_string(),
 152            config: value.config.to_string(),
 153            tcp_connection: value.tcp_connection.map(Into::into),
 154        })
 155    }
 156}
 157
 158impl From<task::DebugRequest> for DebugRequest {
 159    fn from(value: task::DebugRequest) -> Self {
 160        match value {
 161            task::DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()),
 162            task::DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()),
 163        }
 164    }
 165}
 166
 167impl From<DebugRequest> for task::DebugRequest {
 168    fn from(value: DebugRequest) -> Self {
 169        match value {
 170            DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()),
 171            DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()),
 172        }
 173    }
 174}
 175
 176impl From<task::LaunchRequest> for LaunchRequest {
 177    fn from(value: task::LaunchRequest) -> Self {
 178        Self {
 179            program: value.program,
 180            cwd: value.cwd.map(|p| p.to_string_lossy().into_owned()),
 181            args: value.args,
 182            envs: value.env.into_iter().collect(),
 183        }
 184    }
 185}
 186
 187impl From<task::AttachRequest> for AttachRequest {
 188    fn from(value: task::AttachRequest) -> Self {
 189        Self {
 190            process_id: value.process_id,
 191        }
 192    }
 193}
 194
 195impl From<LaunchRequest> for task::LaunchRequest {
 196    fn from(value: LaunchRequest) -> Self {
 197        Self {
 198            program: value.program,
 199            cwd: value.cwd.map(|p| p.into()),
 200            args: value.args,
 201            env: value.envs.into_iter().collect(),
 202        }
 203    }
 204}
 205impl From<AttachRequest> for task::AttachRequest {
 206    fn from(value: AttachRequest) -> Self {
 207        Self {
 208            process_id: value.process_id,
 209        }
 210    }
 211}
 212
 213impl From<ZedDebugConfig> for DebugConfig {
 214    fn from(value: ZedDebugConfig) -> Self {
 215        Self {
 216            label: value.label.into(),
 217            adapter: value.adapter.into(),
 218            request: value.request.into(),
 219            stop_on_entry: value.stop_on_entry,
 220        }
 221    }
 222}
 223impl TryFrom<DebugAdapterBinary> for extension::DebugAdapterBinary {
 224    type Error = anyhow::Error;
 225    fn try_from(value: DebugAdapterBinary) -> Result<Self, Self::Error> {
 226        Ok(Self {
 227            command: value.command,
 228            arguments: value.arguments,
 229            envs: value.envs.into_iter().collect(),
 230            cwd: value.cwd.map(|s| s.into()),
 231            connection: value.connection.map(Into::into),
 232            request_args: value.request_args.try_into()?,
 233        })
 234    }
 235}
 236
 237impl From<BuildTaskDefinition> for extension::BuildTaskDefinition {
 238    fn from(value: BuildTaskDefinition) -> Self {
 239        match value {
 240            BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
 241            BuildTaskDefinition::Template(build_task_template) => Self::Template {
 242                task_template: build_task_template.template.into(),
 243                locator_name: build_task_template.locator_name.map(SharedString::from),
 244            },
 245        }
 246    }
 247}
 248
 249impl From<extension::BuildTaskDefinition> for BuildTaskDefinition {
 250    fn from(value: extension::BuildTaskDefinition) -> Self {
 251        match value {
 252            extension::BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
 253            extension::BuildTaskDefinition::Template {
 254                task_template,
 255                locator_name,
 256            } => Self::Template(BuildTaskDefinitionTemplatePayload {
 257                template: task_template.into(),
 258                locator_name: locator_name.map(String::from),
 259            }),
 260        }
 261    }
 262}
 263impl From<BuildTaskTemplate> for extension::BuildTaskTemplate {
 264    fn from(value: BuildTaskTemplate) -> Self {
 265        Self {
 266            label: value.label,
 267            command: value.command,
 268            args: value.args,
 269            env: value.env.into_iter().collect(),
 270            cwd: value.cwd,
 271            ..Default::default()
 272        }
 273    }
 274}
 275impl From<extension::BuildTaskTemplate> for BuildTaskTemplate {
 276    fn from(value: extension::BuildTaskTemplate) -> Self {
 277        Self {
 278            label: value.label,
 279            command: value.command,
 280            args: value.args,
 281            env: value.env.into_iter().collect(),
 282            cwd: value.cwd,
 283        }
 284    }
 285}
 286
 287impl TryFrom<DebugScenario> for extension::DebugScenario {
 288    type Error = anyhow::Error;
 289
 290    fn try_from(value: DebugScenario) -> std::result::Result<Self, Self::Error> {
 291        Ok(Self {
 292            adapter: value.adapter.into(),
 293            label: value.label.into(),
 294            build: value.build.map(Into::into),
 295            config: serde_json::Value::from_str(&value.config)?,
 296            tcp_connection: value.tcp_connection.map(Into::into),
 297        })
 298    }
 299}
 300
 301impl From<extension::DebugScenario> for DebugScenario {
 302    fn from(value: extension::DebugScenario) -> Self {
 303        Self {
 304            adapter: value.adapter.into(),
 305            label: value.label.into(),
 306            build: value.build.map(Into::into),
 307            config: value.config.to_string(),
 308            tcp_connection: value.tcp_connection.map(Into::into),
 309        }
 310    }
 311}
 312
 313impl TryFrom<SpawnInTerminal> for ResolvedTask {
 314    type Error = anyhow::Error;
 315
 316    fn try_from(value: SpawnInTerminal) -> Result<Self, Self::Error> {
 317        Ok(Self {
 318            label: value.label,
 319            command: value.command.context("missing command")?,
 320            args: value.args,
 321            env: value.env.into_iter().collect(),
 322            cwd: value.cwd.map(|s| {
 323                let s = s.to_string_lossy();
 324                if cfg!(target_os = "windows") {
 325                    s.replace('\\', "/")
 326                } else {
 327                    s.into_owned()
 328                }
 329            }),
 330        })
 331    }
 332}
 333
 334impl From<CodeLabel> for extension::CodeLabel {
 335    fn from(value: CodeLabel) -> Self {
 336        Self {
 337            code: value.code,
 338            spans: value.spans.into_iter().map(Into::into).collect(),
 339            filter_range: value.filter_range.into(),
 340        }
 341    }
 342}
 343
 344impl From<CodeLabelSpan> for extension::CodeLabelSpan {
 345    fn from(value: CodeLabelSpan) -> Self {
 346        match value {
 347            CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
 348            CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
 349        }
 350    }
 351}
 352
 353impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
 354    fn from(value: CodeLabelSpanLiteral) -> Self {
 355        Self {
 356            text: value.text,
 357            highlight_name: value.highlight_name,
 358        }
 359    }
 360}
 361
 362impl From<extension::Completion> for Completion {
 363    fn from(value: extension::Completion) -> Self {
 364        Self {
 365            label: value.label,
 366            label_details: value.label_details.map(Into::into),
 367            detail: value.detail,
 368            kind: value.kind.map(Into::into),
 369            insert_text_format: value.insert_text_format.map(Into::into),
 370        }
 371    }
 372}
 373
 374impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
 375    fn from(value: extension::CompletionLabelDetails) -> Self {
 376        Self {
 377            detail: value.detail,
 378            description: value.description,
 379        }
 380    }
 381}
 382
 383impl From<extension::CompletionKind> for CompletionKind {
 384    fn from(value: extension::CompletionKind) -> Self {
 385        match value {
 386            extension::CompletionKind::Text => Self::Text,
 387            extension::CompletionKind::Method => Self::Method,
 388            extension::CompletionKind::Function => Self::Function,
 389            extension::CompletionKind::Constructor => Self::Constructor,
 390            extension::CompletionKind::Field => Self::Field,
 391            extension::CompletionKind::Variable => Self::Variable,
 392            extension::CompletionKind::Class => Self::Class,
 393            extension::CompletionKind::Interface => Self::Interface,
 394            extension::CompletionKind::Module => Self::Module,
 395            extension::CompletionKind::Property => Self::Property,
 396            extension::CompletionKind::Unit => Self::Unit,
 397            extension::CompletionKind::Value => Self::Value,
 398            extension::CompletionKind::Enum => Self::Enum,
 399            extension::CompletionKind::Keyword => Self::Keyword,
 400            extension::CompletionKind::Snippet => Self::Snippet,
 401            extension::CompletionKind::Color => Self::Color,
 402            extension::CompletionKind::File => Self::File,
 403            extension::CompletionKind::Reference => Self::Reference,
 404            extension::CompletionKind::Folder => Self::Folder,
 405            extension::CompletionKind::EnumMember => Self::EnumMember,
 406            extension::CompletionKind::Constant => Self::Constant,
 407            extension::CompletionKind::Struct => Self::Struct,
 408            extension::CompletionKind::Event => Self::Event,
 409            extension::CompletionKind::Operator => Self::Operator,
 410            extension::CompletionKind::TypeParameter => Self::TypeParameter,
 411            extension::CompletionKind::Other(value) => Self::Other(value),
 412        }
 413    }
 414}
 415
 416impl From<extension::InsertTextFormat> for InsertTextFormat {
 417    fn from(value: extension::InsertTextFormat) -> Self {
 418        match value {
 419            extension::InsertTextFormat::PlainText => Self::PlainText,
 420            extension::InsertTextFormat::Snippet => Self::Snippet,
 421            extension::InsertTextFormat::Other(value) => Self::Other(value),
 422        }
 423    }
 424}
 425
 426impl From<extension::Symbol> for Symbol {
 427    fn from(value: extension::Symbol) -> Self {
 428        Self {
 429            kind: value.kind.into(),
 430            name: value.name,
 431        }
 432    }
 433}
 434
 435impl From<extension::SymbolKind> for SymbolKind {
 436    fn from(value: extension::SymbolKind) -> Self {
 437        match value {
 438            extension::SymbolKind::File => Self::File,
 439            extension::SymbolKind::Module => Self::Module,
 440            extension::SymbolKind::Namespace => Self::Namespace,
 441            extension::SymbolKind::Package => Self::Package,
 442            extension::SymbolKind::Class => Self::Class,
 443            extension::SymbolKind::Method => Self::Method,
 444            extension::SymbolKind::Property => Self::Property,
 445            extension::SymbolKind::Field => Self::Field,
 446            extension::SymbolKind::Constructor => Self::Constructor,
 447            extension::SymbolKind::Enum => Self::Enum,
 448            extension::SymbolKind::Interface => Self::Interface,
 449            extension::SymbolKind::Function => Self::Function,
 450            extension::SymbolKind::Variable => Self::Variable,
 451            extension::SymbolKind::Constant => Self::Constant,
 452            extension::SymbolKind::String => Self::String,
 453            extension::SymbolKind::Number => Self::Number,
 454            extension::SymbolKind::Boolean => Self::Boolean,
 455            extension::SymbolKind::Array => Self::Array,
 456            extension::SymbolKind::Object => Self::Object,
 457            extension::SymbolKind::Key => Self::Key,
 458            extension::SymbolKind::Null => Self::Null,
 459            extension::SymbolKind::EnumMember => Self::EnumMember,
 460            extension::SymbolKind::Struct => Self::Struct,
 461            extension::SymbolKind::Event => Self::Event,
 462            extension::SymbolKind::Operator => Self::Operator,
 463            extension::SymbolKind::TypeParameter => Self::TypeParameter,
 464            extension::SymbolKind::Other(value) => Self::Other(value),
 465        }
 466    }
 467}
 468
 469impl From<extension::SlashCommand> for SlashCommand {
 470    fn from(value: extension::SlashCommand) -> Self {
 471        Self {
 472            name: value.name,
 473            description: value.description,
 474            tooltip_text: value.tooltip_text,
 475            requires_argument: value.requires_argument,
 476        }
 477    }
 478}
 479
 480impl From<SlashCommandOutput> for extension::SlashCommandOutput {
 481    fn from(value: SlashCommandOutput) -> Self {
 482        Self {
 483            text: value.text,
 484            sections: value.sections.into_iter().map(Into::into).collect(),
 485        }
 486    }
 487}
 488
 489impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
 490    fn from(value: SlashCommandOutputSection) -> Self {
 491        Self {
 492            range: value.range.start as usize..value.range.end as usize,
 493            label: value.label,
 494        }
 495    }
 496}
 497
 498impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
 499    fn from(value: SlashCommandArgumentCompletion) -> Self {
 500        Self {
 501            label: value.label,
 502            new_text: value.new_text,
 503            run_command: value.run_command,
 504        }
 505    }
 506}
 507
 508impl TryFrom<ContextServerConfiguration> for extension::ContextServerConfiguration {
 509    type Error = anyhow::Error;
 510
 511    fn try_from(value: ContextServerConfiguration) -> Result<Self, Self::Error> {
 512        let settings_schema: serde_json::Value = serde_json::from_str(&value.settings_schema)
 513            .context("Failed to parse settings_schema")?;
 514
 515        Ok(Self {
 516            installation_instructions: value.installation_instructions,
 517            default_settings: value.default_settings,
 518            settings_schema,
 519        })
 520    }
 521}
 522
 523impl HostKeyValueStore for WasmState {
 524    async fn insert(
 525        &mut self,
 526        kv_store: Resource<ExtensionKeyValueStore>,
 527        key: String,
 528        value: String,
 529    ) -> wasmtime::Result<Result<(), String>> {
 530        let kv_store = self.table.get(&kv_store)?;
 531        kv_store.insert(key, value).await.to_wasmtime_result()
 532    }
 533
 534    async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
 535        // We only ever hand out borrows of key-value stores.
 536        Ok(())
 537    }
 538}
 539
 540impl HostProject for WasmState {
 541    async fn worktree_ids(
 542        &mut self,
 543        project: Resource<ExtensionProject>,
 544    ) -> wasmtime::Result<Vec<u64>> {
 545        let project = self.table.get(&project)?;
 546        Ok(project.worktree_ids())
 547    }
 548
 549    async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
 550        // We only ever hand out borrows of projects.
 551        Ok(())
 552    }
 553}
 554
 555impl HostWorktree for WasmState {
 556    async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
 557        let delegate = self.table.get(&delegate)?;
 558        Ok(delegate.id())
 559    }
 560
 561    async fn root_path(
 562        &mut self,
 563        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 564    ) -> wasmtime::Result<String> {
 565        let delegate = self.table.get(&delegate)?;
 566        Ok(delegate.root_path())
 567    }
 568
 569    async fn read_text_file(
 570        &mut self,
 571        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 572        path: String,
 573    ) -> wasmtime::Result<Result<String, String>> {
 574        let delegate = self.table.get(&delegate)?;
 575        Ok(delegate
 576            .read_text_file(&RelPath::new(Path::new(&path), PathStyle::Posix)?)
 577            .await
 578            .map_err(|error| error.to_string()))
 579    }
 580
 581    async fn shell_env(
 582        &mut self,
 583        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 584    ) -> wasmtime::Result<EnvVars> {
 585        let delegate = self.table.get(&delegate)?;
 586        Ok(delegate.shell_env().await.into_iter().collect())
 587    }
 588
 589    async fn which(
 590        &mut self,
 591        delegate: Resource<Arc<dyn WorktreeDelegate>>,
 592        binary_name: String,
 593    ) -> wasmtime::Result<Option<String>> {
 594        let delegate = self.table.get(&delegate)?;
 595        Ok(delegate.which(binary_name).await)
 596    }
 597
 598    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
 599        // We only ever hand out borrows of worktrees.
 600        Ok(())
 601    }
 602}
 603
 604impl common::Host for WasmState {}
 605
 606impl http_client::Host for WasmState {
 607    async fn fetch(
 608        &mut self,
 609        request: http_client::HttpRequest,
 610    ) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
 611        maybe!(async {
 612            let url = &request.url;
 613            let request = convert_request(&request)?;
 614            let mut response = self.host.http_client.send(request).await?;
 615
 616            if response.status().is_client_error() || response.status().is_server_error() {
 617                bail!("failed to fetch '{url}': status code {}", response.status())
 618            }
 619            convert_response(&mut response).await
 620        })
 621        .await
 622        .to_wasmtime_result()
 623    }
 624
 625    async fn fetch_stream(
 626        &mut self,
 627        request: http_client::HttpRequest,
 628    ) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
 629        let request = convert_request(&request)?;
 630        let response = self.host.http_client.send(request);
 631        maybe!(async {
 632            let response = response.await?;
 633            let stream = Arc::new(Mutex::new(response));
 634            let resource = self.table.push(stream)?;
 635            Ok(resource)
 636        })
 637        .await
 638        .to_wasmtime_result()
 639    }
 640}
 641
 642impl http_client::HostHttpResponseStream for WasmState {
 643    async fn next_chunk(
 644        &mut self,
 645        resource: Resource<ExtensionHttpResponseStream>,
 646    ) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
 647        let stream = self.table.get(&resource)?.clone();
 648        maybe!(async move {
 649            let mut response = stream.lock().await;
 650            let mut buffer = vec![0; 8192]; // 8KB buffer
 651            let bytes_read = response.body_mut().read(&mut buffer).await?;
 652            if bytes_read == 0 {
 653                Ok(None)
 654            } else {
 655                buffer.truncate(bytes_read);
 656                Ok(Some(buffer))
 657            }
 658        })
 659        .await
 660        .to_wasmtime_result()
 661    }
 662
 663    async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
 664        Ok(())
 665    }
 666}
 667
 668impl From<http_client::HttpMethod> for ::http_client::Method {
 669    fn from(value: http_client::HttpMethod) -> Self {
 670        match value {
 671            http_client::HttpMethod::Get => Self::GET,
 672            http_client::HttpMethod::Post => Self::POST,
 673            http_client::HttpMethod::Put => Self::PUT,
 674            http_client::HttpMethod::Delete => Self::DELETE,
 675            http_client::HttpMethod::Head => Self::HEAD,
 676            http_client::HttpMethod::Options => Self::OPTIONS,
 677            http_client::HttpMethod::Patch => Self::PATCH,
 678        }
 679    }
 680}
 681
 682fn convert_request(
 683    extension_request: &http_client::HttpRequest,
 684) -> anyhow::Result<::http_client::Request<AsyncBody>> {
 685    let mut request = ::http_client::Request::builder()
 686        .method(::http_client::Method::from(extension_request.method))
 687        .uri(&extension_request.url)
 688        .follow_redirects(match extension_request.redirect_policy {
 689            http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
 690            http_client::RedirectPolicy::FollowLimit(limit) => {
 691                ::http_client::RedirectPolicy::FollowLimit(limit)
 692            }
 693            http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
 694        });
 695    for (key, value) in &extension_request.headers {
 696        request = request.header(key, value);
 697    }
 698    let body = extension_request
 699        .body
 700        .clone()
 701        .map(AsyncBody::from)
 702        .unwrap_or_default();
 703    request.body(body).map_err(anyhow::Error::from)
 704}
 705
 706async fn convert_response(
 707    response: &mut ::http_client::Response<AsyncBody>,
 708) -> anyhow::Result<http_client::HttpResponse> {
 709    let mut extension_response = http_client::HttpResponse {
 710        body: Vec::new(),
 711        headers: Vec::new(),
 712    };
 713
 714    for (key, value) in response.headers() {
 715        extension_response
 716            .headers
 717            .push((key.to_string(), value.to_str().unwrap_or("").to_string()));
 718    }
 719
 720    response
 721        .body_mut()
 722        .read_to_end(&mut extension_response.body)
 723        .await?;
 724
 725    Ok(extension_response)
 726}
 727
 728impl nodejs::Host for WasmState {
 729    async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
 730        self.host
 731            .node_runtime
 732            .binary_path()
 733            .await
 734            .map(|path| path.to_string_lossy().into_owned())
 735            .to_wasmtime_result()
 736    }
 737
 738    async fn npm_package_latest_version(
 739        &mut self,
 740        package_name: String,
 741    ) -> wasmtime::Result<Result<String, String>> {
 742        self.host
 743            .node_runtime
 744            .npm_package_latest_version(&package_name)
 745            .await
 746            .to_wasmtime_result()
 747    }
 748
 749    async fn npm_package_installed_version(
 750        &mut self,
 751        package_name: String,
 752    ) -> wasmtime::Result<Result<Option<String>, String>> {
 753        self.host
 754            .node_runtime
 755            .npm_package_installed_version(&self.work_dir(), &package_name)
 756            .await
 757            .to_wasmtime_result()
 758    }
 759
 760    async fn npm_install_package(
 761        &mut self,
 762        package_name: String,
 763        version: String,
 764    ) -> wasmtime::Result<Result<(), String>> {
 765        self.capability_granter
 766            .grant_npm_install_package(&package_name)?;
 767
 768        self.host
 769            .node_runtime
 770            .npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
 771            .await
 772            .to_wasmtime_result()
 773    }
 774}
 775
 776#[async_trait]
 777impl lsp::Host for WasmState {}
 778
 779impl From<::http_client::github::GithubRelease> for github::GithubRelease {
 780    fn from(value: ::http_client::github::GithubRelease) -> Self {
 781        Self {
 782            version: value.tag_name,
 783            assets: value.assets.into_iter().map(Into::into).collect(),
 784        }
 785    }
 786}
 787
 788impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
 789    fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
 790        Self {
 791            name: value.name,
 792            download_url: value.browser_download_url,
 793        }
 794    }
 795}
 796
 797impl github::Host for WasmState {
 798    async fn latest_github_release(
 799        &mut self,
 800        repo: String,
 801        options: github::GithubReleaseOptions,
 802    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
 803        maybe!(async {
 804            let release = ::http_client::github::latest_github_release(
 805                &repo,
 806                options.require_assets,
 807                options.pre_release,
 808                self.host.http_client.clone(),
 809            )
 810            .await?;
 811            Ok(release.into())
 812        })
 813        .await
 814        .to_wasmtime_result()
 815    }
 816
 817    async fn github_release_by_tag_name(
 818        &mut self,
 819        repo: String,
 820        tag: String,
 821    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
 822        maybe!(async {
 823            let release = ::http_client::github::get_release_by_tag_name(
 824                &repo,
 825                &tag,
 826                self.host.http_client.clone(),
 827            )
 828            .await?;
 829            Ok(release.into())
 830        })
 831        .await
 832        .to_wasmtime_result()
 833    }
 834}
 835
 836impl platform::Host for WasmState {
 837    async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
 838        Ok((
 839            match env::consts::OS {
 840                "macos" => platform::Os::Mac,
 841                "linux" => platform::Os::Linux,
 842                "windows" => platform::Os::Windows,
 843                _ => panic!("unsupported os"),
 844            },
 845            match env::consts::ARCH {
 846                "aarch64" => platform::Architecture::Aarch64,
 847                "x86" => platform::Architecture::X86,
 848                "x86_64" => platform::Architecture::X8664,
 849                _ => panic!("unsupported architecture"),
 850            },
 851        ))
 852    }
 853}
 854
 855impl From<std::process::Output> for process::Output {
 856    fn from(output: std::process::Output) -> Self {
 857        Self {
 858            status: output.status.code(),
 859            stdout: output.stdout,
 860            stderr: output.stderr,
 861        }
 862    }
 863}
 864
 865impl process::Host for WasmState {
 866    async fn run_command(
 867        &mut self,
 868        command: process::Command,
 869    ) -> wasmtime::Result<Result<process::Output, String>> {
 870        maybe!(async {
 871            self.capability_granter
 872                .grant_exec(&command.command, &command.args)?;
 873
 874            let output = util::command::new_smol_command(command.command.as_str())
 875                .args(&command.args)
 876                .envs(command.env)
 877                .output()
 878                .await?;
 879
 880            Ok(output.into())
 881        })
 882        .await
 883        .to_wasmtime_result()
 884    }
 885}
 886
 887#[async_trait]
 888impl slash_command::Host for WasmState {}
 889
 890#[async_trait]
 891impl context_server::Host for WasmState {}
 892
 893impl dap::Host for WasmState {
 894    async fn resolve_tcp_template(
 895        &mut self,
 896        template: TcpArgumentsTemplate,
 897    ) -> wasmtime::Result<Result<TcpArguments, String>> {
 898        maybe!(async {
 899            let (host, port, timeout) =
 900                ::dap::configure_tcp_connection(task::TcpArgumentsTemplate {
 901                    port: template.port,
 902                    host: template.host.map(Ipv4Addr::from_bits),
 903                    timeout: template.timeout,
 904                })
 905                .await?;
 906            Ok(TcpArguments {
 907                port,
 908                host: host.to_bits(),
 909                timeout,
 910            })
 911        })
 912        .await
 913        .to_wasmtime_result()
 914    }
 915}
 916
 917impl ExtensionImports for WasmState {
 918    async fn get_settings(
 919        &mut self,
 920        location: Option<self::SettingsLocation>,
 921        category: String,
 922        key: Option<String>,
 923    ) -> wasmtime::Result<Result<String, String>> {
 924        self.on_main_thread(|cx| {
 925            async move {
 926                let path = location.as_ref().and_then(|location| {
 927                    RelPath::new(Path::new(&location.path), PathStyle::Posix).ok()
 928                });
 929                let location = path
 930                    .as_ref()
 931                    .zip(location.as_ref())
 932                    .map(|(path, location)| ::settings::SettingsLocation {
 933                        worktree_id: WorktreeId::from_proto(location.worktree_id),
 934                        path,
 935                    });
 936
 937                cx.update(|cx| match category.as_str() {
 938                    "language" => {
 939                        let key = key.map(|k| LanguageName::new(&k));
 940                        let settings = AllLanguageSettings::get(location, cx).language(
 941                            location,
 942                            key.as_ref(),
 943                            cx,
 944                        );
 945                        Ok(serde_json::to_string(&settings::LanguageSettings {
 946                            tab_size: settings.tab_size,
 947                        })?)
 948                    }
 949                    "lsp" => {
 950                        let settings = key
 951                            .and_then(|key| {
 952                                ProjectSettings::get(location, cx)
 953                                    .lsp
 954                                    .get(&::lsp::LanguageServerName::from_proto(key))
 955                            })
 956                            .cloned()
 957                            .unwrap_or_default();
 958                        Ok(serde_json::to_string(&settings::LspSettings {
 959                            binary: settings.binary.map(|binary| settings::CommandSettings {
 960                                path: binary.path,
 961                                arguments: binary.arguments,
 962                                env: binary.env.map(|env| env.into_iter().collect()),
 963                            }),
 964                            settings: settings.settings,
 965                            initialization_options: settings.initialization_options,
 966                        })?)
 967                    }
 968                    "context_servers" => {
 969                        let settings = key
 970                            .and_then(|key| {
 971                                ProjectSettings::get(location, cx)
 972                                    .context_servers
 973                                    .get(key.as_str())
 974                            })
 975                            .cloned()
 976                            .unwrap_or_else(|| {
 977                                project::project_settings::ContextServerSettings::default_extension(
 978                                )
 979                            });
 980
 981                        match settings {
 982                            project::project_settings::ContextServerSettings::Stdio {
 983                                enabled: _,
 984                                command,
 985                            } => Ok(serde_json::to_string(&settings::ContextServerSettings {
 986                                command: Some(settings::CommandSettings {
 987                                    path: command.path.to_str().map(|path| path.to_string()),
 988                                    arguments: Some(command.args),
 989                                    env: command.env.map(|env| env.into_iter().collect()),
 990                                }),
 991                                settings: None,
 992                            })?),
 993                            project::project_settings::ContextServerSettings::Extension {
 994                                enabled: _,
 995                                settings,
 996                            } => Ok(serde_json::to_string(&settings::ContextServerSettings {
 997                                command: None,
 998                                settings: Some(settings),
 999                            })?),
1000                            project::project_settings::ContextServerSettings::Http { .. } => {
1001                                bail!("remote context server settings not supported in 0.6.0")
1002                            }
1003                        }
1004                    }
1005                    _ => {
1006                        bail!("Unknown settings category: {}", category);
1007                    }
1008                })
1009            }
1010            .boxed_local()
1011        })
1012        .await?
1013        .to_wasmtime_result()
1014    }
1015
1016    async fn set_language_server_installation_status(
1017        &mut self,
1018        server_name: String,
1019        status: LanguageServerInstallationStatus,
1020    ) -> wasmtime::Result<()> {
1021        let status = match status {
1022            LanguageServerInstallationStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
1023            LanguageServerInstallationStatus::Downloading => BinaryStatus::Downloading,
1024            LanguageServerInstallationStatus::None => BinaryStatus::None,
1025            LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
1026        };
1027
1028        self.host
1029            .proxy
1030            .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
1031
1032        Ok(())
1033    }
1034
1035    async fn download_file(
1036        &mut self,
1037        url: String,
1038        path: String,
1039        file_type: DownloadedFileType,
1040    ) -> wasmtime::Result<Result<(), String>> {
1041        maybe!(async {
1042            let parsed_url = Url::parse(&url)?;
1043            self.capability_granter.grant_download_file(&parsed_url)?;
1044
1045            let path = PathBuf::from(path);
1046            let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
1047
1048            self.host.fs.create_dir(&extension_work_dir).await?;
1049
1050            let destination_path = self
1051                .host
1052                .writeable_path_from_extension(&self.manifest.id, &path)?;
1053
1054            let mut response = self
1055                .host
1056                .http_client
1057                .get(&url, Default::default(), true)
1058                .await
1059                .context("downloading release")?;
1060
1061            anyhow::ensure!(
1062                response.status().is_success(),
1063                "download failed with status {}",
1064                response.status()
1065            );
1066            let body = BufReader::new(response.body_mut());
1067
1068            match file_type {
1069                DownloadedFileType::Uncompressed => {
1070                    futures::pin_mut!(body);
1071                    self.host
1072                        .fs
1073                        .create_file_with(&destination_path, body)
1074                        .await?;
1075                }
1076                DownloadedFileType::Gzip => {
1077                    let body = GzipDecoder::new(body);
1078                    futures::pin_mut!(body);
1079                    self.host
1080                        .fs
1081                        .create_file_with(&destination_path, body)
1082                        .await?;
1083                }
1084                DownloadedFileType::GzipTar => {
1085                    let body = GzipDecoder::new(body);
1086                    futures::pin_mut!(body);
1087                    self.host
1088                        .fs
1089                        .extract_tar_file(&destination_path, Archive::new(body))
1090                        .await?;
1091                }
1092                DownloadedFileType::Zip => {
1093                    futures::pin_mut!(body);
1094                    extract_zip(&destination_path, body)
1095                        .await
1096                        .with_context(|| format!("unzipping {path:?} archive"))?;
1097                }
1098            }
1099
1100            Ok(())
1101        })
1102        .await
1103        .to_wasmtime_result()
1104    }
1105
1106    async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
1107        let path = self
1108            .host
1109            .writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
1110
1111        make_file_executable(&path)
1112            .await
1113            .with_context(|| format!("setting permissions for path {path:?}"))
1114            .to_wasmtime_result()
1115    }
1116
1117    // =========================================================================
1118    // LLM Provider Import Implementations
1119    // =========================================================================
1120
1121    async fn llm_request_credential(
1122        &mut self,
1123        _provider_id: String,
1124        _credential_type: llm_provider::CredentialType,
1125        _label: String,
1126        _placeholder: String,
1127    ) -> wasmtime::Result<Result<bool, String>> {
1128        // For now, credential requests return false (not provided)
1129        // Extensions should use llm_get_env_var to check for env vars first,
1130        // then llm_store_credential/llm_get_credential for manual storage
1131        // Full UI credential prompting will be added in a future phase
1132        Ok(Ok(false))
1133    }
1134
1135    async fn llm_get_credential(
1136        &mut self,
1137        provider_id: String,
1138    ) -> wasmtime::Result<Option<String>> {
1139        let extension_id = self.manifest.id.clone();
1140        let credential_key = format!("extension-llm-{}:{}", extension_id, provider_id);
1141
1142        self.on_main_thread(move |cx| {
1143            async move {
1144                let credentials_provider = cx.update(|cx| <dyn CredentialsProvider>::global(cx))?;
1145                let result = credentials_provider
1146                    .read_credentials(&credential_key, cx)
1147                    .await
1148                    .ok()
1149                    .flatten();
1150                Ok(result.map(|(_, password)| String::from_utf8_lossy(&password).to_string()))
1151            }
1152            .boxed_local()
1153        })
1154        .await
1155    }
1156
1157    async fn llm_store_credential(
1158        &mut self,
1159        provider_id: String,
1160        value: String,
1161    ) -> wasmtime::Result<Result<(), String>> {
1162        let extension_id = self.manifest.id.clone();
1163        let credential_key = format!("extension-llm-{}:{}", extension_id, provider_id);
1164
1165        self.on_main_thread(move |cx| {
1166            async move {
1167                let credentials_provider = cx.update(|cx| <dyn CredentialsProvider>::global(cx))?;
1168                credentials_provider
1169                    .write_credentials(&credential_key, "api_key", value.as_bytes(), cx)
1170                    .await
1171                    .map_err(|e| anyhow::anyhow!("{}", e))
1172            }
1173            .boxed_local()
1174        })
1175        .await
1176        .to_wasmtime_result()
1177    }
1178
1179    async fn llm_delete_credential(
1180        &mut self,
1181        provider_id: String,
1182    ) -> wasmtime::Result<Result<(), String>> {
1183        let extension_id = self.manifest.id.clone();
1184        let credential_key = format!("extension-llm-{}:{}", extension_id, provider_id);
1185
1186        self.on_main_thread(move |cx| {
1187            async move {
1188                let credentials_provider = cx.update(|cx| <dyn CredentialsProvider>::global(cx))?;
1189                credentials_provider
1190                    .delete_credentials(&credential_key, cx)
1191                    .await
1192                    .map_err(|e| anyhow::anyhow!("{}", e))
1193            }
1194            .boxed_local()
1195        })
1196        .await
1197        .to_wasmtime_result()
1198    }
1199
1200    async fn llm_get_env_var(&mut self, name: String) -> wasmtime::Result<Option<String>> {
1201        let extension_id = self.manifest.id.clone();
1202
1203        // Find which provider (if any) declares this env var in its auth config
1204        let mut allowed_provider_id: Option<Arc<str>> = None;
1205        for (provider_id, provider_entry) in &self.manifest.language_model_providers {
1206            if let Some(auth_config) = &provider_entry.auth {
1207                if auth_config.env_var.as_deref() == Some(&name) {
1208                    allowed_provider_id = Some(provider_id.clone());
1209                    break;
1210                }
1211            }
1212        }
1213
1214        // If no provider declares this env var, deny access
1215        let Some(provider_id) = allowed_provider_id else {
1216            log::warn!(
1217                "Extension {} attempted to read env var {} which is not declared in any provider auth config",
1218                extension_id,
1219                name
1220            );
1221            return Ok(None);
1222        };
1223
1224        // Check if the user has allowed this provider to read env vars
1225        let full_provider_id = format!("{}:{}", extension_id, provider_id);
1226        let is_allowed = self
1227            .on_main_thread(move |cx| {
1228                async move {
1229                    cx.update(|cx| {
1230                        ExtensionSettings::get_global(cx)
1231                            .allowed_env_var_providers
1232                            .contains(full_provider_id.as_str())
1233                    })
1234                    .unwrap_or(false)
1235                }
1236                .boxed_local()
1237            })
1238            .await;
1239
1240        if !is_allowed {
1241            log::debug!(
1242                "Extension {} provider {} is not allowed to read env var {}",
1243                extension_id,
1244                provider_id,
1245                name
1246            );
1247            return Ok(None);
1248        }
1249
1250        Ok(env::var(&name).ok())
1251    }
1252
1253    async fn llm_oauth_start_web_auth(
1254        &mut self,
1255        config: llm_provider::OauthWebAuthConfig,
1256    ) -> wasmtime::Result<Result<llm_provider::OauthWebAuthResult, String>> {
1257        let auth_url = config.auth_url;
1258        let callback_path = config.callback_path;
1259        let timeout_secs = config.timeout_secs.unwrap_or(300);
1260
1261        self.on_main_thread(move |cx| {
1262            async move {
1263                let listener = TcpListener::bind("127.0.0.1:0")
1264                    .await
1265                    .map_err(|e| anyhow::anyhow!("Failed to bind localhost server: {}", e))?;
1266                let port = listener
1267                    .local_addr()
1268                    .map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?
1269                    .port();
1270
1271                cx.update(|cx| {
1272                    cx.open_url(&auth_url);
1273                })?;
1274
1275                let accept_future = async {
1276                    let (mut stream, _) = listener
1277                        .accept()
1278                        .await
1279                        .map_err(|e| anyhow::anyhow!("Failed to accept connection: {}", e))?;
1280
1281                    let mut request_line = String::new();
1282                    {
1283                        let mut reader = smol::io::BufReader::new(&mut stream);
1284                        smol::io::AsyncBufReadExt::read_line(&mut reader, &mut request_line)
1285                            .await
1286                            .map_err(|e| anyhow::anyhow!("Failed to read request: {}", e))?;
1287                    }
1288
1289                    let callback_url = if let Some(path_start) = request_line.find(' ') {
1290                        if let Some(path_end) = request_line[path_start + 1..].find(' ') {
1291                            let path = &request_line[path_start + 1..path_start + 1 + path_end];
1292                            if path.starts_with(&callback_path) || path.starts_with(&format!("/{}", callback_path.trim_start_matches('/'))) {
1293                                format!("http://localhost:{}{}", port, path)
1294                            } else {
1295                                return Err(anyhow::anyhow!(
1296                                    "Unexpected callback path: {}",
1297                                    path
1298                                ));
1299                            }
1300                        } else {
1301                            return Err(anyhow::anyhow!("Malformed HTTP request"));
1302                        }
1303                    } else {
1304                        return Err(anyhow::anyhow!("Malformed HTTP request"));
1305                    };
1306
1307                    let response = "HTTP/1.1 200 OK\r\n\
1308                        Content-Type: text/html\r\n\
1309                        Connection: close\r\n\
1310                        \r\n\
1311                        <!DOCTYPE html>\
1312                        <html><head><title>Authentication Complete</title></head>\
1313                        <body style=\"font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;\">\
1314                        <div style=\"text-align: center;\">\
1315                        <h1>Authentication Complete</h1>\
1316                        <p>You can close this window and return to Zed.</p>\
1317                        </div></body></html>";
1318
1319                    smol::io::AsyncWriteExt::write_all(&mut stream, response.as_bytes())
1320                        .await
1321                        .ok();
1322                    smol::io::AsyncWriteExt::flush(&mut stream).await.ok();
1323
1324                    Ok(callback_url)
1325                };
1326
1327                let timeout_duration = Duration::from_secs(timeout_secs as u64);
1328                let callback_url = smol::future::or(
1329                    accept_future,
1330                    async {
1331                        smol::Timer::after(timeout_duration).await;
1332                        Err(anyhow::anyhow!(
1333                            "OAuth callback timed out after {} seconds",
1334                            timeout_secs
1335                        ))
1336                    },
1337                )
1338                .await?;
1339
1340                Ok(llm_provider::OauthWebAuthResult {
1341                    callback_url,
1342                    port: port as u32,
1343                })
1344            }
1345            .boxed_local()
1346        })
1347        .await
1348        .to_wasmtime_result()
1349    }
1350
1351    async fn llm_oauth_http_request(
1352        &mut self,
1353        request: llm_provider::OauthHttpRequest,
1354    ) -> wasmtime::Result<Result<llm_provider::OauthHttpResponse, String>> {
1355        let http_client = self.host.http_client.clone();
1356
1357        self.on_main_thread(move |_cx| {
1358            async move {
1359                let method = match request.method.to_uppercase().as_str() {
1360                    "GET" => ::http_client::Method::GET,
1361                    "POST" => ::http_client::Method::POST,
1362                    "PUT" => ::http_client::Method::PUT,
1363                    "DELETE" => ::http_client::Method::DELETE,
1364                    "PATCH" => ::http_client::Method::PATCH,
1365                    _ => {
1366                        return Err(anyhow::anyhow!(
1367                            "Unsupported HTTP method: {}",
1368                            request.method
1369                        ));
1370                    }
1371                };
1372
1373                let mut builder = ::http_client::Request::builder()
1374                    .method(method)
1375                    .uri(&request.url);
1376
1377                for (key, value) in &request.headers {
1378                    builder = builder.header(key.as_str(), value.as_str());
1379                }
1380
1381                let body = if request.body.is_empty() {
1382                    AsyncBody::empty()
1383                } else {
1384                    AsyncBody::from(request.body.into_bytes())
1385                };
1386
1387                let http_request = builder
1388                    .body(body)
1389                    .map_err(|e| anyhow::anyhow!("Failed to build request: {}", e))?;
1390
1391                let mut response = http_client
1392                    .send(http_request)
1393                    .await
1394                    .map_err(|e| anyhow::anyhow!("HTTP request failed: {}", e))?;
1395
1396                let status = response.status().as_u16();
1397                let headers: Vec<(String, String)> = response
1398                    .headers()
1399                    .iter()
1400                    .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
1401                    .collect();
1402
1403                let mut body_bytes = Vec::new();
1404                futures::AsyncReadExt::read_to_end(response.body_mut(), &mut body_bytes)
1405                    .await
1406                    .map_err(|e| anyhow::anyhow!("Failed to read response body: {}", e))?;
1407
1408                let body = String::from_utf8_lossy(&body_bytes).to_string();
1409
1410                Ok(llm_provider::OauthHttpResponse {
1411                    status,
1412                    headers,
1413                    body,
1414                })
1415            }
1416            .boxed_local()
1417        })
1418        .await
1419        .to_wasmtime_result()
1420    }
1421
1422    async fn llm_oauth_open_browser(
1423        &mut self,
1424        url: String,
1425    ) -> 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
1440// =============================================================================
1441// LLM Provider Host Implementations
1442// =============================================================================
1443
1444impl llm_provider::Host for WasmState {}
1445
1446// =============================================================================
1447// LLM Provider Type Conversions (v0.7.0 -> latest/v0.8.0)
1448// =============================================================================
1449
1450use super::since_v0_8_0 as latest;
1451
1452impl From<llm_provider::ProviderInfo> for latest::llm_provider::ProviderInfo {
1453    fn from(value: llm_provider::ProviderInfo) -> Self {
1454        Self {
1455            id: value.id,
1456            name: value.name,
1457            icon: value.icon,
1458        }
1459    }
1460}
1461
1462impl From<llm_provider::ModelInfo> for latest::llm_provider::ModelInfo {
1463    fn from(value: llm_provider::ModelInfo) -> Self {
1464        Self {
1465            id: value.id,
1466            name: value.name,
1467            max_token_count: value.max_token_count,
1468            max_output_tokens: value.max_output_tokens,
1469            capabilities: value.capabilities.into(),
1470            is_default: value.is_default,
1471            is_default_fast: value.is_default_fast,
1472        }
1473    }
1474}
1475
1476impl From<llm_provider::ModelCapabilities> for latest::llm_provider::ModelCapabilities {
1477    fn from(value: llm_provider::ModelCapabilities) -> Self {
1478        Self {
1479            supports_images: value.supports_images,
1480            supports_tools: value.supports_tools,
1481            supports_tool_choice_auto: value.supports_tool_choice_auto,
1482            supports_tool_choice_any: value.supports_tool_choice_any,
1483            supports_tool_choice_none: value.supports_tool_choice_none,
1484            supports_thinking: value.supports_thinking,
1485            tool_input_format: value.tool_input_format.into(),
1486        }
1487    }
1488}
1489
1490impl From<llm_provider::ToolInputFormat> for latest::llm_provider::ToolInputFormat {
1491    fn from(value: llm_provider::ToolInputFormat) -> Self {
1492        match value {
1493            llm_provider::ToolInputFormat::JsonSchema => Self::JsonSchema,
1494            llm_provider::ToolInputFormat::Simplified => Self::Simplified,
1495        }
1496    }
1497}
1498
1499impl From<llm_provider::CompletionEvent> for latest::llm_provider::CompletionEvent {
1500    fn from(value: llm_provider::CompletionEvent) -> Self {
1501        match value {
1502            llm_provider::CompletionEvent::Started => Self::Started,
1503            llm_provider::CompletionEvent::Text(s) => Self::Text(s),
1504            llm_provider::CompletionEvent::Thinking(t) => Self::Thinking(t.into()),
1505            llm_provider::CompletionEvent::RedactedThinking(s) => Self::RedactedThinking(s),
1506            llm_provider::CompletionEvent::ToolUse(t) => Self::ToolUse(t.into()),
1507            llm_provider::CompletionEvent::ToolUseJsonParseError(e) => {
1508                Self::ToolUseJsonParseError(e.into())
1509            }
1510            llm_provider::CompletionEvent::Stop(r) => Self::Stop(r.into()),
1511            llm_provider::CompletionEvent::Usage(u) => Self::Usage(u.into()),
1512            llm_provider::CompletionEvent::ReasoningDetails(s) => Self::ReasoningDetails(s),
1513        }
1514    }
1515}
1516
1517impl From<llm_provider::ThinkingContent> for latest::llm_provider::ThinkingContent {
1518    fn from(value: llm_provider::ThinkingContent) -> Self {
1519        Self {
1520            text: value.text,
1521            signature: value.signature,
1522        }
1523    }
1524}
1525
1526impl From<llm_provider::ToolUse> for latest::llm_provider::ToolUse {
1527    fn from(value: llm_provider::ToolUse) -> Self {
1528        Self {
1529            id: value.id,
1530            name: value.name,
1531            input: value.input,
1532            thought_signature: value.thought_signature,
1533        }
1534    }
1535}
1536
1537impl From<llm_provider::ToolUseJsonParseError> for latest::llm_provider::ToolUseJsonParseError {
1538    fn from(value: llm_provider::ToolUseJsonParseError) -> Self {
1539        Self {
1540            id: value.id,
1541            tool_name: value.tool_name,
1542            raw_input: value.raw_input,
1543            error: value.error,
1544        }
1545    }
1546}
1547
1548impl From<llm_provider::StopReason> for latest::llm_provider::StopReason {
1549    fn from(value: llm_provider::StopReason) -> Self {
1550        match value {
1551            llm_provider::StopReason::EndTurn => Self::EndTurn,
1552            llm_provider::StopReason::MaxTokens => Self::MaxTokens,
1553            llm_provider::StopReason::ToolUse => Self::ToolUse,
1554            llm_provider::StopReason::Refusal => Self::Refusal,
1555        }
1556    }
1557}
1558
1559impl From<llm_provider::TokenUsage> for latest::llm_provider::TokenUsage {
1560    fn from(value: llm_provider::TokenUsage) -> Self {
1561        Self {
1562            input_tokens: value.input_tokens,
1563            output_tokens: value.output_tokens,
1564            cache_creation_input_tokens: value.cache_creation_input_tokens,
1565            cache_read_input_tokens: value.cache_read_input_tokens,
1566        }
1567    }
1568}
1569
1570impl From<llm_provider::CacheConfiguration> for latest::llm_provider::CacheConfiguration {
1571    fn from(value: llm_provider::CacheConfiguration) -> Self {
1572        Self {
1573            max_cache_anchors: value.max_cache_anchors,
1574            should_cache_tool_definitions: value.should_cache_tool_definitions,
1575            min_total_token_count: value.min_total_token_count,
1576        }
1577    }
1578}
1579
1580// Conversions from latest (v0.8.0) -> v0.7.0 for requests
1581
1582impl From<latest::llm_provider::CompletionRequest> for llm_provider::CompletionRequest {
1583    fn from(value: latest::llm_provider::CompletionRequest) -> Self {
1584        Self {
1585            messages: value.messages.into_iter().map(Into::into).collect(),
1586            tools: value.tools.into_iter().map(Into::into).collect(),
1587            tool_choice: value.tool_choice.map(Into::into),
1588            stop_sequences: value.stop_sequences,
1589            temperature: value.temperature,
1590            thinking_allowed: value.thinking_allowed,
1591            max_tokens: value.max_tokens,
1592        }
1593    }
1594}
1595
1596impl From<latest::llm_provider::RequestMessage> for llm_provider::RequestMessage {
1597    fn from(value: latest::llm_provider::RequestMessage) -> Self {
1598        Self {
1599            role: value.role.into(),
1600            content: value.content.into_iter().map(Into::into).collect(),
1601            cache: value.cache,
1602        }
1603    }
1604}
1605
1606impl From<latest::llm_provider::MessageRole> for llm_provider::MessageRole {
1607    fn from(value: latest::llm_provider::MessageRole) -> Self {
1608        match value {
1609            latest::llm_provider::MessageRole::User => Self::User,
1610            latest::llm_provider::MessageRole::Assistant => Self::Assistant,
1611            latest::llm_provider::MessageRole::System => Self::System,
1612        }
1613    }
1614}
1615
1616impl From<latest::llm_provider::MessageContent> for llm_provider::MessageContent {
1617    fn from(value: latest::llm_provider::MessageContent) -> Self {
1618        match value {
1619            latest::llm_provider::MessageContent::Text(s) => Self::Text(s),
1620            latest::llm_provider::MessageContent::Image(i) => Self::Image(i.into()),
1621            latest::llm_provider::MessageContent::ToolUse(t) => Self::ToolUse(t.into()),
1622            latest::llm_provider::MessageContent::ToolResult(t) => Self::ToolResult(t.into()),
1623            latest::llm_provider::MessageContent::Thinking(t) => Self::Thinking(t.into()),
1624            latest::llm_provider::MessageContent::RedactedThinking(s) => Self::RedactedThinking(s),
1625        }
1626    }
1627}
1628
1629impl From<latest::llm_provider::ImageData> for llm_provider::ImageData {
1630    fn from(value: latest::llm_provider::ImageData) -> Self {
1631        Self {
1632            source: value.source,
1633            width: value.width,
1634            height: value.height,
1635        }
1636    }
1637}
1638
1639impl From<latest::llm_provider::ToolUse> for llm_provider::ToolUse {
1640    fn from(value: latest::llm_provider::ToolUse) -> Self {
1641        Self {
1642            id: value.id,
1643            name: value.name,
1644            input: value.input,
1645            thought_signature: value.thought_signature,
1646        }
1647    }
1648}
1649
1650impl From<latest::llm_provider::ToolResult> for llm_provider::ToolResult {
1651    fn from(value: latest::llm_provider::ToolResult) -> Self {
1652        Self {
1653            tool_use_id: value.tool_use_id,
1654            tool_name: value.tool_name,
1655            is_error: value.is_error,
1656            content: value.content.into(),
1657        }
1658    }
1659}
1660
1661impl From<latest::llm_provider::ToolResultContent> for llm_provider::ToolResultContent {
1662    fn from(value: latest::llm_provider::ToolResultContent) -> Self {
1663        match value {
1664            latest::llm_provider::ToolResultContent::Text(s) => Self::Text(s),
1665            latest::llm_provider::ToolResultContent::Image(i) => Self::Image(i.into()),
1666        }
1667    }
1668}
1669
1670impl From<latest::llm_provider::ThinkingContent> for llm_provider::ThinkingContent {
1671    fn from(value: latest::llm_provider::ThinkingContent) -> Self {
1672        Self {
1673            text: value.text,
1674            signature: value.signature,
1675        }
1676    }
1677}
1678
1679impl From<latest::llm_provider::ToolDefinition> for llm_provider::ToolDefinition {
1680    fn from(value: latest::llm_provider::ToolDefinition) -> Self {
1681        Self {
1682            name: value.name,
1683            description: value.description,
1684            input_schema: value.input_schema,
1685        }
1686    }
1687}
1688
1689impl From<latest::llm_provider::ToolChoice> for llm_provider::ToolChoice {
1690    fn from(value: latest::llm_provider::ToolChoice) -> Self {
1691        match value {
1692            latest::llm_provider::ToolChoice::Auto => Self::Auto,
1693            latest::llm_provider::ToolChoice::Any => Self::Any,
1694            latest::llm_provider::ToolChoice::None => Self::None,
1695        }
1696    }
1697}
1698
1699// =============================================================================
1700// Command Type Conversions (v0.7.0 -> latest/v0.8.0)
1701// =============================================================================
1702
1703impl From<Command> for latest::Command {
1704    fn from(value: Command) -> Self {
1705        Self {
1706            command: value.command,
1707            args: value.args,
1708            env: value.env,
1709        }
1710    }
1711}
1712
1713// =============================================================================
1714// LSP Type Conversions (latest/v0.8.0 -> v0.7.0)
1715// =============================================================================
1716
1717impl From<latest::lsp::Completion> for lsp::Completion {
1718    fn from(value: latest::lsp::Completion) -> Self {
1719        Self {
1720            label: value.label,
1721            label_details: value.label_details.map(Into::into),
1722            detail: value.detail,
1723            kind: value.kind.map(Into::into),
1724            insert_text_format: value.insert_text_format.map(Into::into),
1725        }
1726    }
1727}
1728
1729impl From<latest::lsp::CompletionLabelDetails> for lsp::CompletionLabelDetails {
1730    fn from(value: latest::lsp::CompletionLabelDetails) -> Self {
1731        Self {
1732            detail: value.detail,
1733            description: value.description,
1734        }
1735    }
1736}
1737
1738impl From<latest::lsp::CompletionKind> for lsp::CompletionKind {
1739    fn from(value: latest::lsp::CompletionKind) -> Self {
1740        match value {
1741            latest::lsp::CompletionKind::Text => Self::Text,
1742            latest::lsp::CompletionKind::Method => Self::Method,
1743            latest::lsp::CompletionKind::Function => Self::Function,
1744            latest::lsp::CompletionKind::Constructor => Self::Constructor,
1745            latest::lsp::CompletionKind::Field => Self::Field,
1746            latest::lsp::CompletionKind::Variable => Self::Variable,
1747            latest::lsp::CompletionKind::Class => Self::Class,
1748            latest::lsp::CompletionKind::Interface => Self::Interface,
1749            latest::lsp::CompletionKind::Module => Self::Module,
1750            latest::lsp::CompletionKind::Property => Self::Property,
1751            latest::lsp::CompletionKind::Unit => Self::Unit,
1752            latest::lsp::CompletionKind::Value => Self::Value,
1753            latest::lsp::CompletionKind::Enum => Self::Enum,
1754            latest::lsp::CompletionKind::Keyword => Self::Keyword,
1755            latest::lsp::CompletionKind::Snippet => Self::Snippet,
1756            latest::lsp::CompletionKind::Color => Self::Color,
1757            latest::lsp::CompletionKind::File => Self::File,
1758            latest::lsp::CompletionKind::Reference => Self::Reference,
1759            latest::lsp::CompletionKind::Folder => Self::Folder,
1760            latest::lsp::CompletionKind::EnumMember => Self::EnumMember,
1761            latest::lsp::CompletionKind::Constant => Self::Constant,
1762            latest::lsp::CompletionKind::Struct => Self::Struct,
1763            latest::lsp::CompletionKind::Event => Self::Event,
1764            latest::lsp::CompletionKind::Operator => Self::Operator,
1765            latest::lsp::CompletionKind::TypeParameter => Self::TypeParameter,
1766            latest::lsp::CompletionKind::Other(n) => Self::Other(n),
1767        }
1768    }
1769}
1770
1771impl From<latest::lsp::InsertTextFormat> for lsp::InsertTextFormat {
1772    fn from(value: latest::lsp::InsertTextFormat) -> Self {
1773        match value {
1774            latest::lsp::InsertTextFormat::PlainText => Self::PlainText,
1775            latest::lsp::InsertTextFormat::Snippet => Self::Snippet,
1776            latest::lsp::InsertTextFormat::Other(n) => Self::Other(n),
1777        }
1778    }
1779}
1780
1781impl From<latest::lsp::Symbol> for lsp::Symbol {
1782    fn from(value: latest::lsp::Symbol) -> Self {
1783        Self {
1784            kind: value.kind.into(),
1785            name: value.name,
1786        }
1787    }
1788}
1789
1790impl From<latest::lsp::SymbolKind> for lsp::SymbolKind {
1791    fn from(value: latest::lsp::SymbolKind) -> Self {
1792        match value {
1793            latest::lsp::SymbolKind::File => Self::File,
1794            latest::lsp::SymbolKind::Module => Self::Module,
1795            latest::lsp::SymbolKind::Namespace => Self::Namespace,
1796            latest::lsp::SymbolKind::Package => Self::Package,
1797            latest::lsp::SymbolKind::Class => Self::Class,
1798            latest::lsp::SymbolKind::Method => Self::Method,
1799            latest::lsp::SymbolKind::Property => Self::Property,
1800            latest::lsp::SymbolKind::Field => Self::Field,
1801            latest::lsp::SymbolKind::Constructor => Self::Constructor,
1802            latest::lsp::SymbolKind::Enum => Self::Enum,
1803            latest::lsp::SymbolKind::Interface => Self::Interface,
1804            latest::lsp::SymbolKind::Function => Self::Function,
1805            latest::lsp::SymbolKind::Variable => Self::Variable,
1806            latest::lsp::SymbolKind::Constant => Self::Constant,
1807            latest::lsp::SymbolKind::String => Self::String,
1808            latest::lsp::SymbolKind::Number => Self::Number,
1809            latest::lsp::SymbolKind::Boolean => Self::Boolean,
1810            latest::lsp::SymbolKind::Array => Self::Array,
1811            latest::lsp::SymbolKind::Object => Self::Object,
1812            latest::lsp::SymbolKind::Key => Self::Key,
1813            latest::lsp::SymbolKind::Null => Self::Null,
1814            latest::lsp::SymbolKind::EnumMember => Self::EnumMember,
1815            latest::lsp::SymbolKind::Struct => Self::Struct,
1816            latest::lsp::SymbolKind::Event => Self::Event,
1817            latest::lsp::SymbolKind::Operator => Self::Operator,
1818            latest::lsp::SymbolKind::TypeParameter => Self::TypeParameter,
1819            latest::lsp::SymbolKind::Other(n) => Self::Other(n),
1820        }
1821    }
1822}
1823
1824// =============================================================================
1825// CodeLabel Type Conversions (v0.7.0 -> latest/v0.8.0)
1826// =============================================================================
1827
1828impl From<CodeLabel> for latest::CodeLabel {
1829    fn from(value: CodeLabel) -> Self {
1830        Self {
1831            code: value.code,
1832            spans: value.spans.into_iter().map(Into::into).collect(),
1833            filter_range: value.filter_range.into(),
1834        }
1835    }
1836}
1837
1838impl From<CodeLabelSpan> for latest::CodeLabelSpan {
1839    fn from(value: CodeLabelSpan) -> Self {
1840        match value {
1841            CodeLabelSpan::CodeRange(r) => Self::CodeRange(r.into()),
1842            CodeLabelSpan::Literal(l) => Self::Literal(l.into()),
1843        }
1844    }
1845}
1846
1847impl From<CodeLabelSpanLiteral> for latest::CodeLabelSpanLiteral {
1848    fn from(value: CodeLabelSpanLiteral) -> Self {
1849        Self {
1850            text: value.text,
1851            highlight_name: value.highlight_name,
1852        }
1853    }
1854}
1855
1856impl From<Range> for latest::Range {
1857    fn from(value: Range) -> Self {
1858        Self {
1859            start: value.start,
1860            end: value.end,
1861        }
1862    }
1863}
1864
1865// =============================================================================
1866// SlashCommand Type Conversions (latest/v0.8.0 -> v0.7.0)
1867// =============================================================================
1868
1869impl From<&latest::SlashCommand> for slash_command::SlashCommand {
1870    fn from(value: &latest::SlashCommand) -> Self {
1871        Self {
1872            name: value.name.clone(),
1873            description: value.description.clone(),
1874            tooltip_text: value.tooltip_text.clone(),
1875            requires_argument: value.requires_argument,
1876        }
1877    }
1878}
1879
1880// =============================================================================
1881// SlashCommand Type Conversions (v0.7.0 -> latest/v0.8.0)
1882// =============================================================================
1883
1884impl From<slash_command::SlashCommandArgumentCompletion>
1885    for latest::SlashCommandArgumentCompletion
1886{
1887    fn from(value: slash_command::SlashCommandArgumentCompletion) -> Self {
1888        Self {
1889            label: value.label,
1890            new_text: value.new_text,
1891            run_command: value.run_command,
1892        }
1893    }
1894}
1895
1896impl From<slash_command::SlashCommandOutput> for latest::SlashCommandOutput {
1897    fn from(value: slash_command::SlashCommandOutput) -> Self {
1898        Self {
1899            sections: value.sections.into_iter().map(Into::into).collect(),
1900            text: value.text,
1901        }
1902    }
1903}
1904
1905impl From<SlashCommandOutputSection> for latest::slash_command::SlashCommandOutputSection {
1906    fn from(value: SlashCommandOutputSection) -> Self {
1907        Self {
1908            range: value.range.into(),
1909            label: value.label,
1910        }
1911    }
1912}
1913
1914// =============================================================================
1915// ContextServer Type Conversions (v0.7.0 -> latest/v0.8.0)
1916// =============================================================================
1917
1918impl From<context_server::ContextServerConfiguration>
1919    for latest::context_server::ContextServerConfiguration
1920{
1921    fn from(value: context_server::ContextServerConfiguration) -> Self {
1922        Self {
1923            installation_instructions: value.installation_instructions,
1924            settings_schema: value.settings_schema,
1925            default_settings: value.default_settings,
1926        }
1927    }
1928}
1929
1930// =============================================================================
1931// DAP Type Conversions (v0.7.0 -> latest/v0.8.0)
1932// =============================================================================
1933
1934impl From<dap::DebugAdapterBinary> for latest::dap::DebugAdapterBinary {
1935    fn from(value: dap::DebugAdapterBinary) -> Self {
1936        Self {
1937            command: value.command,
1938            arguments: value.arguments,
1939            envs: value.envs,
1940            cwd: value.cwd,
1941            connection: value.connection.map(|c| latest::dap::TcpArguments {
1942                host: c.host,
1943                port: c.port,
1944                timeout: c.timeout,
1945            }),
1946            request_args: latest::dap::StartDebuggingRequestArguments {
1947                configuration: value.request_args.configuration,
1948                request: match value.request_args.request {
1949                    dap::StartDebuggingRequestArgumentsRequest::Launch => {
1950                        latest::dap::StartDebuggingRequestArgumentsRequest::Launch
1951                    }
1952                    dap::StartDebuggingRequestArgumentsRequest::Attach => {
1953                        latest::dap::StartDebuggingRequestArgumentsRequest::Attach
1954                    }
1955                },
1956            },
1957        }
1958    }
1959}
1960
1961impl From<dap::StartDebuggingRequestArgumentsRequest>
1962    for latest::dap::StartDebuggingRequestArgumentsRequest
1963{
1964    fn from(value: dap::StartDebuggingRequestArgumentsRequest) -> Self {
1965        match value {
1966            dap::StartDebuggingRequestArgumentsRequest::Launch => Self::Launch,
1967            dap::StartDebuggingRequestArgumentsRequest::Attach => Self::Attach,
1968        }
1969    }
1970}
1971
1972impl From<dap::DebugScenario> for latest::dap::DebugScenario {
1973    fn from(value: dap::DebugScenario) -> Self {
1974        Self {
1975            adapter: value.adapter,
1976            label: value.label,
1977            build: value.build.map(|b| match b {
1978                dap::BuildTaskDefinition::ByName(name) => {
1979                    latest::dap::BuildTaskDefinition::ByName(name)
1980                }
1981                dap::BuildTaskDefinition::Template(t) => {
1982                    latest::dap::BuildTaskDefinition::Template(
1983                        latest::dap::BuildTaskDefinitionTemplatePayload {
1984                            locator_name: t.locator_name,
1985                            template: latest::dap::BuildTaskTemplate {
1986                                label: t.template.label,
1987                                command: t.template.command,
1988                                args: t.template.args,
1989                                env: t.template.env,
1990                                cwd: t.template.cwd,
1991                            },
1992                        },
1993                    )
1994                }
1995            }),
1996            config: value.config,
1997            tcp_connection: value
1998                .tcp_connection
1999                .map(|t| latest::dap::TcpArgumentsTemplate {
2000                    host: t.host,
2001                    port: t.port,
2002                    timeout: t.timeout,
2003                }),
2004        }
2005    }
2006}
2007
2008impl From<dap::DebugRequest> for latest::dap::DebugRequest {
2009    fn from(value: dap::DebugRequest) -> Self {
2010        match value {
2011            dap::DebugRequest::Attach(a) => Self::Attach(latest::dap::AttachRequest {
2012                process_id: a.process_id,
2013            }),
2014            dap::DebugRequest::Launch(l) => Self::Launch(latest::dap::LaunchRequest {
2015                program: l.program,
2016                cwd: l.cwd,
2017                args: l.args,
2018                envs: l.envs,
2019            }),
2020        }
2021    }
2022}