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