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}