1use proc_macro::TokenStream;
7use proc_macro2::Span;
8use quote::quote;
9use syn::{
10 Expr, ItemStruct, Lit, Meta, MetaList, Type, parse_macro_input, parse_quote,
11 punctuated::Punctuated, spanned::Spanned, token::Comma,
12};
13
14#[proc_macro_attribute]
15pub fn askit(attr: TokenStream, item: TokenStream) -> TokenStream {
16 askit_agent(attr, item)
17}
18
19#[proc_macro_attribute]
36pub fn askit_agent(attr: TokenStream, item: TokenStream) -> TokenStream {
37 let args = parse_macro_input!(attr with Punctuated<Meta, Comma>::parse_terminated);
38 let item_struct = parse_macro_input!(item as ItemStruct);
39
40 match expand_askit_agent(args, item_struct) {
41 Ok(tokens) => tokens.into(),
42 Err(err) => err.into_compile_error().into(),
43 }
44}
45
46struct AgentArgs {
47 kind: Option<Expr>,
48 name: Option<Expr>,
49 title: Option<Expr>,
50 description: Option<Expr>,
51 category: Option<Expr>,
52 inputs: Vec<Expr>,
53 outputs: Vec<Expr>,
54 configs: Vec<ConfigSpec>,
55 global_configs: Vec<ConfigSpec>,
56 displays: Vec<DisplaySpec>,
57}
58
59#[derive(Default)]
60struct CommonConfig {
61 name: Option<Expr>,
62 default: Option<Expr>,
63 title: Option<Expr>,
64 description: Option<Expr>,
65}
66
67enum ConfigSpec {
68 Unit(CommonConfig),
69 Boolean(CommonConfig),
70 Integer(CommonConfig),
71 Number(CommonConfig),
72 String(CommonConfig),
73 Text(CommonConfig),
74 Object(CommonConfig),
75}
76
77enum DisplaySpec {
78 Unit(CommonDisplay),
79 Boolean(CommonDisplay),
80 Integer(CommonDisplay),
81 Number(CommonDisplay),
82 String(CommonDisplay),
83 Text(CommonDisplay),
84 Object(CommonDisplay),
85 Any(CommonDisplay),
86}
87
88#[derive(Default)]
89struct CommonDisplay {
90 name: Option<Expr>,
91 title: Option<Expr>,
92 description: Option<Expr>,
93 hide_title: bool,
94}
95
96fn expand_askit_agent(
97 args: Punctuated<Meta, Comma>,
98 item: ItemStruct,
99) -> syn::Result<proc_macro2::TokenStream> {
100 let has_data_field = item.fields.iter().any(|f| match (&f.ident, &f.ty) {
101 (Some(ident), Type::Path(tp)) if ident == "data" => tp
102 .path
103 .segments
104 .last()
105 .map(|seg| seg.ident == "AgentData")
106 .unwrap_or(false),
107 _ => false,
108 });
109
110 if !has_data_field {
111 return Err(syn::Error::new(
112 item.span(),
113 "#[askit_agent] expects the struct to have a `data: AgentData` field",
114 ));
115 }
116
117 let mut parsed = AgentArgs {
118 kind: None,
119 name: None,
120 title: None,
121 description: None,
122 category: None,
123 inputs: Vec::new(),
124 outputs: Vec::new(),
125 configs: Vec::new(),
126 global_configs: Vec::new(),
127 displays: Vec::new(),
128 };
129
130 for meta in args {
131 match meta {
132 Meta::NameValue(nv) if nv.path.is_ident("kind") => {
133 parsed.kind = Some(nv.value);
134 }
135 Meta::NameValue(nv) if nv.path.is_ident("name") => {
136 parsed.name = Some(nv.value);
137 }
138 Meta::NameValue(nv) if nv.path.is_ident("title") => {
139 parsed.title = Some(nv.value);
140 }
141 Meta::NameValue(nv) if nv.path.is_ident("description") => {
142 parsed.description = Some(nv.value);
143 }
144 Meta::NameValue(nv) if nv.path.is_ident("category") => {
145 parsed.category = Some(nv.value);
146 }
147 Meta::NameValue(nv) if nv.path.is_ident("inputs") => {
148 parsed.inputs = parse_expr_array(nv.value)?;
149 }
150 Meta::NameValue(nv) if nv.path.is_ident("outputs") => {
151 parsed.outputs = parse_expr_array(nv.value)?;
152 }
153 Meta::List(ml) if ml.path.is_ident("inputs") => {
154 parsed.inputs = collect_exprs(ml)?;
155 }
156 Meta::List(ml) if ml.path.is_ident("outputs") => {
157 parsed.outputs = collect_exprs(ml)?;
158 }
159 Meta::List(ml) if ml.path.is_ident("string_config") => {
160 parsed
161 .configs
162 .push(ConfigSpec::String(parse_common_config(ml)?));
163 }
164 Meta::List(ml) if ml.path.is_ident("text_config") => {
165 parsed
166 .configs
167 .push(ConfigSpec::Text(parse_common_config(ml)?));
168 }
169 Meta::List(ml) if ml.path.is_ident("boolean_config") => {
170 parsed
171 .configs
172 .push(ConfigSpec::Boolean(parse_common_config(ml)?));
173 }
174 Meta::List(ml) if ml.path.is_ident("integer_config") => {
175 parsed
176 .configs
177 .push(ConfigSpec::Integer(parse_common_config(ml)?));
178 }
179 Meta::List(ml) if ml.path.is_ident("number_config") => {
180 parsed
181 .configs
182 .push(ConfigSpec::Number(parse_common_config(ml)?));
183 }
184 Meta::List(ml) if ml.path.is_ident("object_config") => {
185 parsed
186 .configs
187 .push(ConfigSpec::Object(parse_common_config(ml)?));
188 }
189 Meta::List(ml) if ml.path.is_ident("unit_config") => {
190 parsed
191 .configs
192 .push(ConfigSpec::Unit(parse_common_config(ml)?));
193 }
194 Meta::List(ml) if ml.path.is_ident("string_global_config") => {
195 parsed
196 .global_configs
197 .push(ConfigSpec::String(parse_common_config(ml)?));
198 }
199 Meta::List(ml) if ml.path.is_ident("text_global_config") => {
200 parsed
201 .global_configs
202 .push(ConfigSpec::Text(parse_common_config(ml)?));
203 }
204 Meta::List(ml) if ml.path.is_ident("boolean_global_config") => {
205 parsed
206 .global_configs
207 .push(ConfigSpec::Boolean(parse_common_config(ml)?));
208 }
209 Meta::List(ml) if ml.path.is_ident("integer_global_config") => {
210 parsed
211 .global_configs
212 .push(ConfigSpec::Integer(parse_common_config(ml)?));
213 }
214 Meta::List(ml) if ml.path.is_ident("number_global_config") => {
215 parsed
216 .global_configs
217 .push(ConfigSpec::Number(parse_common_config(ml)?));
218 }
219 Meta::List(ml) if ml.path.is_ident("object_global_config") => {
220 parsed
221 .global_configs
222 .push(ConfigSpec::Object(parse_common_config(ml)?));
223 }
224 Meta::List(ml) if ml.path.is_ident("unit_global_config") => {
225 parsed
226 .global_configs
227 .push(ConfigSpec::Unit(parse_common_config(ml)?));
228 }
229 Meta::List(ml) if ml.path.is_ident("unit_display") => {
230 parsed
231 .displays
232 .push(DisplaySpec::Unit(parse_common_display(ml)?));
233 }
234 Meta::List(ml) if ml.path.is_ident("boolean_display") => {
235 parsed
236 .displays
237 .push(DisplaySpec::Boolean(parse_common_display(ml)?));
238 }
239 Meta::List(ml) if ml.path.is_ident("integer_display") => {
240 parsed
241 .displays
242 .push(DisplaySpec::Integer(parse_common_display(ml)?));
243 }
244 Meta::List(ml) if ml.path.is_ident("number_display") => {
245 parsed
246 .displays
247 .push(DisplaySpec::Number(parse_common_display(ml)?));
248 }
249 Meta::List(ml) if ml.path.is_ident("string_display") => {
250 parsed
251 .displays
252 .push(DisplaySpec::String(parse_common_display(ml)?));
253 }
254 Meta::List(ml) if ml.path.is_ident("text_display") => {
255 parsed
256 .displays
257 .push(DisplaySpec::Text(parse_common_display(ml)?));
258 }
259 Meta::List(ml) if ml.path.is_ident("object_display") => {
260 parsed
261 .displays
262 .push(DisplaySpec::Object(parse_common_display(ml)?));
263 }
264 Meta::List(ml) if ml.path.is_ident("any_display") => {
265 parsed
266 .displays
267 .push(DisplaySpec::Any(parse_common_display(ml)?));
268 }
269 other => {
270 return Err(syn::Error::new_spanned(
271 other,
272 "unsupported askit_agent argument",
273 ));
274 }
275 }
276 }
277
278 let ident = &item.ident;
279 let generics = item.generics.clone();
280 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
281 let data_impl = quote! {
282 impl #impl_generics ::agent_stream_kit::HasAgentData for #ident #ty_generics #where_clause {
283 fn data(&self) -> &::agent_stream_kit::AgentData {
284 &self.data
285 }
286
287 fn mut_data(&mut self) -> &mut ::agent_stream_kit::AgentData {
288 &mut self.data
289 }
290 }
291 };
292
293 let kind = parsed.kind.unwrap_or_else(|| parse_quote! { "Agent" });
294 let name_tokens = parsed.name.map(|n| quote! { #n }).unwrap_or_else(|| {
295 quote! { concat!(module_path!(), "::", stringify!(#ident)) }
296 });
297
298 let title = parsed
299 .title
300 .ok_or_else(|| syn::Error::new(Span::call_site(), "askit_agent: missing `title`"))?;
301 let category = parsed
302 .category
303 .ok_or_else(|| syn::Error::new(Span::call_site(), "askit_agent: missing `category`"))?;
304 let title = quote! { .title(#title) };
305 let description = parsed.description.map(|d| quote! { .description(#d) });
306 let category = quote! { .category(#category) };
307
308 let inputs = if parsed.inputs.is_empty() {
309 quote! {}
310 } else {
311 let values = parsed.inputs;
312 quote! { .inputs(vec![#(#values),*]) }
313 };
314
315 let outputs = if parsed.outputs.is_empty() {
316 quote! {}
317 } else {
318 let values = parsed.outputs;
319 quote! { .outputs(vec![#(#values),*]) }
320 };
321
322 let config_calls = parsed
323 .configs
324 .into_iter()
325 .map(|cfg| match cfg {
326 ConfigSpec::Unit(c) => {
327 let name = c.name.ok_or_else(|| {
328 syn::Error::new(Span::call_site(), "unit_config missing `name`")
329 })?;
330 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
331 let description = c
332 .description
333 .map(|d| quote! { let entry = entry.description(#d); });
334 Ok(quote! {
335 .unit_config_with(#name, |entry| {
336 let entry = entry;
337 #title
338 #description
339 entry
340 })
341 })
342 }
343 ConfigSpec::Boolean(c) => {
344 let name = c.name.ok_or_else(|| {
345 syn::Error::new(Span::call_site(), "boolean_config missing `name`")
346 })?;
347 let default = c.default.unwrap_or_else(|| parse_quote! { false });
348 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
349 let description = c
350 .description
351 .map(|d| quote! { let entry = entry.description(#d); });
352 Ok(quote! {
353 .boolean_config_with(#name, #default, |entry| {
354 let entry = entry;
355 #title
356 #description
357 entry
358 })
359 })
360 }
361 ConfigSpec::Integer(c) => {
362 let name = c.name.ok_or_else(|| {
363 syn::Error::new(Span::call_site(), "integer_config missing `name`")
364 })?;
365 let default = c.default.unwrap_or_else(|| parse_quote! { 0i64 });
366 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
367 let description = c
368 .description
369 .map(|d| quote! { let entry = entry.description(#d); });
370 Ok(quote! {
371 .integer_config_with(#name, #default, |entry| {
372 let entry = entry;
373 #title
374 #description
375 entry
376 })
377 })
378 }
379 ConfigSpec::Number(c) => {
380 let name = c.name.ok_or_else(|| {
381 syn::Error::new(Span::call_site(), "number_config missing `name`")
382 })?;
383 let default = c.default.unwrap_or_else(|| parse_quote! { 0.0f64 });
384 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
385 let description = c
386 .description
387 .map(|d| quote! { let entry = entry.description(#d); });
388 Ok(quote! {
389 .number_config_with(#name, #default, |entry| {
390 let entry = entry;
391 #title
392 #description
393 entry
394 })
395 })
396 }
397 ConfigSpec::String(c) => {
398 let name = c.name.ok_or_else(|| {
399 syn::Error::new(Span::call_site(), "string_config missing `name`")
400 })?;
401 let default = c.default.unwrap_or_else(|| parse_quote! { "" });
402 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
403 let description = c
404 .description
405 .map(|d| quote! { let entry = entry.description(#d); });
406 Ok(quote! {
407 .string_config_with(#name, #default, |entry| {
408 let entry = entry;
409 #title
410 #description
411 entry
412 })
413 })
414 }
415 ConfigSpec::Text(c) => {
416 let name = c.name.ok_or_else(|| {
417 syn::Error::new(Span::call_site(), "text_config missing `name`")
418 })?;
419 let default = c.default.unwrap_or_else(|| parse_quote! { "" });
420 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
421 let description = c
422 .description
423 .map(|d| quote! { let entry = entry.description(#d); });
424 Ok(quote! {
425 .text_config_with(#name, #default, |entry| {
426 let entry = entry;
427 #title
428 #description
429 entry
430 })
431 })
432 }
433 ConfigSpec::Object(c) => {
434 let name = c.name.ok_or_else(|| {
435 syn::Error::new(Span::call_site(), "object_config missing `name`")
436 })?;
437 let default = c.default.unwrap_or_else(|| {
438 parse_quote! { ::agent_stream_kit::AgentValue::object_default() }
439 });
440 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
441 let description = c
442 .description
443 .map(|d| quote! { let entry = entry.description(#d); });
444 Ok(quote! {
445 .object_config_with(#name, #default, |entry| {
446 let entry = entry;
447 #title
448 #description
449 entry
450 })
451 })
452 }
453 })
454 .collect::<syn::Result<Vec<_>>>()?;
455
456 let global_config_calls = parsed
457 .global_configs
458 .into_iter()
459 .map(|cfg| match cfg {
460 ConfigSpec::Unit(c) => {
461 let name = c.name.ok_or_else(|| {
462 syn::Error::new(Span::call_site(), "unit_global_config missing `name`")
463 })?;
464 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
465 let description = c
466 .description
467 .map(|d| quote! { let entry = entry.description(#d); });
468 Ok(quote! {
469 .unit_global_config_with(#name, |entry| {
470 let entry = entry;
471 #title
472 #description
473 entry
474 })
475 })
476 }
477 ConfigSpec::Boolean(c) => {
478 let name = c.name.ok_or_else(|| {
479 syn::Error::new(Span::call_site(), "boolean_global_config missing `name`")
480 })?;
481 let default = c.default.unwrap_or_else(|| parse_quote! { false });
482 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
483 let description = c
484 .description
485 .map(|d| quote! { let entry = entry.description(#d); });
486 Ok(quote! {
487 .boolean_global_config_with(#name, #default, |entry| {
488 let entry = entry;
489 #title
490 #description
491 entry
492 })
493 })
494 }
495 ConfigSpec::Integer(c) => {
496 let name = c.name.ok_or_else(|| {
497 syn::Error::new(Span::call_site(), "integer_global_config missing `name`")
498 })?;
499 let default = c.default.unwrap_or_else(|| parse_quote! { 0i64 });
500 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
501 let description = c
502 .description
503 .map(|d| quote! { let entry = entry.description(#d); });
504 Ok(quote! {
505 .integer_global_config_with(#name, #default, |entry| {
506 let entry = entry;
507 #title
508 #description
509 entry
510 })
511 })
512 }
513 ConfigSpec::Number(c) => {
514 let name = c.name.ok_or_else(|| {
515 syn::Error::new(Span::call_site(), "number_global_config missing `name`")
516 })?;
517 let default = c.default.unwrap_or_else(|| parse_quote! { 0.0f64 });
518 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
519 let description = c
520 .description
521 .map(|d| quote! { let entry = entry.description(#d); });
522 Ok(quote! {
523 .number_global_config_with(#name, #default, |entry| {
524 let entry = entry;
525 #title
526 #description
527 entry
528 })
529 })
530 }
531 ConfigSpec::String(c) => {
532 let name = c.name.ok_or_else(|| {
533 syn::Error::new(Span::call_site(), "string_global_config missing `name`")
534 })?;
535 let default = c.default.unwrap_or_else(|| parse_quote! { "" });
536 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
537 let description = c
538 .description
539 .map(|d| quote! { let entry = entry.description(#d); });
540 Ok(quote! {
541 .string_global_config_with(#name, #default, |entry| {
542 let entry = entry;
543 #title
544 #description
545 entry
546 })
547 })
548 }
549 ConfigSpec::Text(c) => {
550 let name = c.name.ok_or_else(|| {
551 syn::Error::new(Span::call_site(), "text_global_config missing `name`")
552 })?;
553 let default = c.default.unwrap_or_else(|| parse_quote! { "" });
554 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
555 let description = c
556 .description
557 .map(|d| quote! { let entry = entry.description(#d); });
558 Ok(quote! {
559 .text_global_config_with(#name, #default, |entry| {
560 let entry = entry;
561 #title
562 #description
563 entry
564 })
565 })
566 }
567 ConfigSpec::Object(c) => {
568 let name = c.name.ok_or_else(|| {
569 syn::Error::new(Span::call_site(), "object_global_config missing `name`")
570 })?;
571 let default = c.default.unwrap_or_else(|| {
572 parse_quote! { ::agent_stream_kit::AgentValue::object_default() }
573 });
574 let title = c.title.map(|t| quote! { let entry = entry.title(#t); });
575 let description = c
576 .description
577 .map(|d| quote! { let entry = entry.description(#d); });
578 Ok(quote! {
579 .object_global_config_with(#name, #default, |entry| {
580 let entry = entry;
581 #title
582 #description
583 entry
584 })
585 })
586 }
587 })
588 .collect::<syn::Result<Vec<_>>>()?;
589
590 let display_calls = parsed
591 .displays
592 .into_iter()
593 .map(|disp| match disp {
594 DisplaySpec::Unit(c) => display_call("unit", c),
595 DisplaySpec::Boolean(c) => display_call("boolean", c),
596 DisplaySpec::Integer(c) => display_call("integer", c),
597 DisplaySpec::Number(c) => display_call("number", c),
598 DisplaySpec::String(c) => display_call("string", c),
599 DisplaySpec::Text(c) => display_call("text", c),
600 DisplaySpec::Object(c) => display_call("object", c),
601 DisplaySpec::Any(c) => display_call("*", c),
602 })
603 .collect::<syn::Result<Vec<_>>>()?;
604
605 let definition_builder = quote! {
606 ::agent_stream_kit::AgentDefinition::new(
607 #kind,
608 #name_tokens,
609 Some(::agent_stream_kit::new_agent_boxed::<#ident>),
610 )
611 #title
612 #description
613 #category
614 #inputs
615 #outputs
616 #(#config_calls)*
617 #(#global_config_calls)*
618 #(#display_calls)*
619 };
620
621 let expanded = quote! {
622 #item
623
624 #data_impl
625
626 impl #impl_generics #ident #ty_generics #where_clause {
627 pub fn agent_definition() -> ::agent_stream_kit::AgentDefinition {
628 #definition_builder
629 }
630
631 pub fn register(askit: &::agent_stream_kit::ASKit) {
632 askit.register_agent(Self::agent_definition());
633 }
634 }
635
636 ::agent_stream_kit::inventory::submit! {
637 ::agent_stream_kit::AgentRegistration {
638 build: || #definition_builder,
639 }
640 }
641 };
642
643 Ok(expanded)
644}
645
646fn collect_exprs(list: MetaList) -> syn::Result<Vec<Expr>> {
647 let values = list.parse_args_with(Punctuated::<Expr, Comma>::parse_terminated)?;
648 Ok(values.into_iter().collect())
649}
650
651fn parse_expr_array(expr: Expr) -> syn::Result<Vec<Expr>> {
652 if let Expr::Array(arr) = expr {
653 Ok(arr.elems.into_iter().collect())
654 } else {
655 Err(syn::Error::new_spanned(
656 expr,
657 "inputs/outputs expect array expressions",
658 ))
659 }
660}
661
662fn parse_common_config(list: MetaList) -> syn::Result<CommonConfig> {
663 let mut cfg = CommonConfig::default();
664 let nested = list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?;
665
666 for meta in nested {
667 match meta {
668 Meta::NameValue(nv) if nv.path.is_ident("name") => {
669 cfg.name = Some(match &nv.value {
670 Expr::Lit(expr_lit) => match &expr_lit.lit {
671 Lit::Str(s) => syn::parse_str::<Expr>(&s.value())?,
672 _ => nv.value.clone(),
673 },
674 _ => nv.value.clone(),
675 });
676 }
677 Meta::NameValue(nv) if nv.path.is_ident("default") => {
678 cfg.default = Some(nv.value.clone());
679 }
680 Meta::NameValue(nv) if nv.path.is_ident("title") => {
681 cfg.title = Some(nv.value.clone());
682 }
683 Meta::NameValue(nv) if nv.path.is_ident("description") => {
684 cfg.description = Some(nv.value.clone());
685 }
686 other => {
687 return Err(syn::Error::new_spanned(
688 other,
689 "config supports name, default, title, description",
690 ));
691 }
692 }
693 }
694
695 if cfg.name.is_none() {
696 return Err(syn::Error::new(list.span(), "config missing `name`"));
697 }
698 Ok(cfg)
699}
700
701fn parse_common_display(list: MetaList) -> syn::Result<CommonDisplay> {
702 let mut cfg = CommonDisplay::default();
703 let nested = list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?;
704
705 for meta in nested {
706 match meta {
707 Meta::NameValue(nv) if nv.path.is_ident("name") => {
708 cfg.name = Some(match &nv.value {
709 Expr::Lit(expr_lit) => match &expr_lit.lit {
710 Lit::Str(s) => syn::parse_str::<Expr>(&s.value())?,
711 _ => nv.value.clone(),
712 },
713 _ => nv.value.clone(),
714 });
715 }
716 Meta::NameValue(nv) if nv.path.is_ident("title") => {
717 cfg.title = Some(nv.value.clone());
718 }
719 Meta::NameValue(nv) if nv.path.is_ident("description") => {
720 cfg.description = Some(nv.value.clone());
721 }
722 Meta::Path(p) if p.is_ident("hide_title") => {
723 cfg.hide_title = true;
724 }
725 other => {
726 return Err(syn::Error::new_spanned(
727 other,
728 "display supports name, title, description, hide_title",
729 ));
730 }
731 }
732 }
733
734 if cfg.name.is_none() {
735 return Err(syn::Error::new(list.span(), "display missing `name`"));
736 }
737 Ok(cfg)
738}
739
740fn display_call(type_name: &str, cfg: CommonDisplay) -> syn::Result<proc_macro2::TokenStream> {
741 let name = cfg
742 .name
743 .ok_or_else(|| syn::Error::new(Span::call_site(), "display missing `name`"))?;
744 let title = cfg.title.map(|t| quote! { let entry = entry.title(#t); });
745 let description = cfg
746 .description
747 .map(|d| quote! { let entry = entry.description(#d); });
748 let hide_title = if cfg.hide_title {
749 quote! { let entry = entry.hide_title(); }
750 } else {
751 quote! {}
752 };
753
754 Ok(quote! {
755 .custom_display_config_with(#name, #type_name, |entry| {
756 let entry = entry;
757 #title
758 #description
759 #hide_title
760 entry
761 })
762 })
763}