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