1use core::str::FromStr;
8
9extern crate alloc;
10use alloc::borrow::{Cow, ToOwned};
11use alloc::format;
12use alloc::string::String;
13use alloc::vec;
14use alloc::vec::Vec;
15
16use crate::common::config;
17use crate::{
18 ChatCompletionsResponse, Choice, LastData, Message, Priority, PromptOpts, ReasoningConfig,
19 ReasoningEffort, Role, Usage,
20};
21
22impl ChatCompletionsResponse {
23 pub fn from_json(json: &str) -> Result<Self, Cow<'static, str>> {
24 let mut p = Parser::new(json);
25 p.skip_ws();
26 p.expect(b'{')?;
27
28 let mut provider = None;
29 let mut model = None;
30 let mut choices = vec![];
31 let mut usage = None;
32
33 loop {
34 p.skip_ws();
35 if p.try_consume(b'}') {
36 break;
37 }
38
39 let key = p
40 .parse_simple_str()
41 .map_err(|err| format!("ChatCompletionsResponse parsing key: {err}"))?;
42 p.skip_ws();
43 p.expect(b':')?;
44 p.skip_ws();
45
46 match key {
47 "provider" => {
48 if provider.is_some() {
49 return Err("duplicate field: provider".into());
50 }
51 provider = Some(p.parse_string()?);
52 }
53 "model" => {
54 if model.is_some() {
55 return Err("duplicate field: model".into());
56 }
57 model = Some(p.parse_string()?);
58 }
59 "choices" => {
60 if !choices.is_empty() {
61 return Err("duplicate field: choices".into());
62 }
63 if !p.try_consume(b'[') {
64 return Err("keys: Expected array".into());
65 }
66 loop {
67 let j = p.value_slice()?;
68 let choice = Choice::from_json(j)?;
69 choices.push(choice);
70 p.skip_ws();
71 if p.try_consume(b',') {
72 continue;
73 }
74 p.skip_ws();
75 if p.try_consume(b']') {
76 break;
77 }
78 }
79 }
80 "usage" => {
81 let j = p.value_slice()?;
82 usage = Some(Usage::from_json(j)?);
83 }
84 _ => {
85 p.skip_value()?;
86 }
87 }
88
89 p.skip_ws();
90 if p.try_consume(b',') {
91 continue;
92 }
93 p.skip_ws();
94 if p.try_consume(b'}') {
95 break;
96 }
97 }
98
99 Ok(ChatCompletionsResponse {
100 provider,
101 model,
102 choices,
103 usage,
104 })
105 }
106}
107
108impl Choice {
109 pub fn from_json(json: &str) -> Result<Self, String> {
110 let mut p = Parser::new(json);
111 p.skip_ws();
112 p.expect(b'{')?;
113
114 let mut delta = None;
115
116 'top: loop {
117 p.skip_ws();
118 if p.try_consume(b'}') {
119 break;
120 }
121
122 let key = p
123 .parse_simple_str()
124 .map_err(|err| format!("Choice::from_json parsing key: {err}"))?;
125 p.skip_ws();
126 p.expect(b':')?;
127 p.skip_ws();
128
129 match key {
130 "delta" => {
131 let j = p.value_slice()?;
132 delta = Some(Message::from_json(j)?);
133 break 'top;
134 }
135 _ => {
136 p.skip_value()?;
137 }
138 }
139
140 p.skip_ws();
141 if p.try_consume(b',') {
142 continue;
143 }
144 p.skip_ws();
145 if p.try_consume(b'}') {
146 break;
147 }
148 }
149
150 Ok(Choice {
151 delta: delta.expect("Missing delta in message"),
152 })
153 }
154}
155
156impl Usage {
157 pub fn from_json(json: &str) -> Result<Self, String> {
158 let mut p = Parser::new(json);
159 p.skip_ws();
160 p.expect(b'{')?;
161
162 let mut cost = 0.0;
164
165 'top: loop {
166 p.skip_ws();
167 if p.try_consume(b'}') {
168 break;
169 }
170
171 let key = p
172 .parse_simple_str()
173 .map_err(|err| format!("Usage parsing key: {err}"))?;
174 p.skip_ws();
175 p.expect(b':')?;
176 p.skip_ws();
177
178 match key {
179 "cost" => {
180 cost = p.parse_f32()?;
181 break 'top;
183 }
184 _ => {
185 p.skip_value()?;
186 }
187 }
188
189 p.skip_ws();
190 if p.try_consume(b',') {
191 continue;
192 }
193 p.skip_ws();
194 if p.try_consume(b'}') {
195 break;
196 }
197 }
198
199 Ok(Usage { cost })
200 }
201}
202
203impl LastData {
204 pub fn from_json(json: &str) -> Result<Self, Cow<'static, str>> {
205 if json.is_empty() {
206 return Err(
207 "Cannot continue, last-<$TMUX_PANE>.json file is empty. Usually that mains previous run failed.".into(),
208 );
209 }
210 let mut p = Parser::new(json);
211 p.skip_ws();
212 p.expect(b'{')?;
213
214 let mut opts = None;
215 let mut messages = vec![];
216
217 loop {
218 p.skip_ws();
219 if p.try_consume(b'}') {
220 break;
221 }
222
223 let key = p
224 .parse_simple_str()
225 .map_err(|err| format!("LastData parsing key: {err}"))?;
226 p.skip_ws();
227 p.expect(b':')?;
228 p.skip_ws();
229
230 match key {
231 "opts" => {
232 if opts.is_some() {
233 return Err("duplicate field: opts".into());
234 }
235 let j = p.value_slice()?;
236 opts = Some(PromptOpts::from_json(j)?);
237 }
238 "messages" => {
239 if !messages.is_empty() {
240 return Err("duplicate field: messages".into());
241 }
242 if !p.try_consume(b'[') {
243 return Err("messages: Expected array".into());
244 }
245 loop {
246 let j = p.value_slice()?;
247 let msg = Message::from_json(j)?;
248 messages.push(msg);
249 p.skip_ws();
250 if p.try_consume(b',') {
251 continue;
252 }
253 p.skip_ws();
254 if p.try_consume(b']') {
255 break;
256 }
257 }
258 }
259 _ => return Err("unknown field".into()),
260 }
261
262 p.skip_ws();
263 if p.try_consume(b',') {
264 continue;
265 }
266 p.skip_ws();
267 if p.try_consume(b'}') {
268 break;
269 }
270 }
271
272 Ok(LastData {
273 opts: opts.expect("Missing prompt opts"),
274 messages,
275 })
276 }
277}
278
279impl Message {
280 pub fn from_json(json: &str) -> Result<Self, Cow<'static, str>> {
281 let mut p = Parser::new(json);
282 p.skip_ws();
283 p.expect(b'{')?;
284
285 let mut role = None;
286 let mut content = None;
287 let mut reasoning = None;
288
289 loop {
290 p.skip_ws();
291 if p.try_consume(b'}') {
292 break;
293 }
294
295 let key = p
296 .parse_simple_str()
297 .map_err(|err| format!("Message parsing key: {err}"))?;
298 p.skip_ws();
299 p.expect(b':')?;
300 p.skip_ws();
301
302 match key {
303 "role" => {
304 if role.is_some() {
305 return Err("duplicate field: role".into());
306 }
307 let r = p.parse_simple_str()?;
308 role = Some(Role::from_str(r)?);
309 }
310 "content" => {
311 if content.is_some() {
312 return Err("duplicate field: content".into());
313 }
314 if p.peek_is_null() {
315 p.parse_null()?;
316 content = None;
317 } else {
318 content = Some(p.parse_string()?);
319 }
320 }
321 "reasoning" => {
322 if reasoning.is_some() {
323 return Err("duplicate field: reasoning".into());
324 }
325 if p.peek_is_null() {
326 p.parse_null()?;
327 reasoning = None
328 } else {
329 reasoning = Some(p.parse_string()?);
330 }
331 }
332 _ => {
333 p.skip_value()?;
334 }
335 }
336
337 p.skip_ws();
338 if p.try_consume(b',') {
339 continue;
340 }
341 p.skip_ws();
342 if p.try_consume(b'}') {
343 break;
344 }
345 }
346
347 Ok(Message::new(
348 role.expect("Missing Role"),
349 content,
350 reasoning,
351 ))
352 }
353}
354
355impl ReasoningConfig {
356 pub fn from_json(json: &str) -> Result<ReasoningConfig, Cow<'static, str>> {
357 let mut p = Parser::new(json);
358 p.skip_ws();
359 p.expect(b'{')?;
360
361 let mut enabled: Option<bool> = None;
362 let mut effort: Option<ReasoningEffort> = None;
363 let mut tokens: Option<u32> = None;
364
365 loop {
366 p.skip_ws();
367 if p.try_consume(b'}') {
368 break;
369 }
370
371 let key = p
373 .parse_simple_str()
374 .map_err(|err| format!("ReasoningConfig parsing key: {err}"))?;
375 p.skip_ws();
376 p.expect(b':')?;
377 p.skip_ws();
378
379 match key {
381 "enabled" => {
382 if enabled.is_some() {
383 return Err("duplicate field: enabled".into());
384 }
385 if p.peek_is_null() {
386 p.parse_null()?;
387 enabled = None;
388 } else {
389 enabled = Some(p.parse_bool()?);
390 }
391 }
392 "effort" => {
393 if effort.is_some() {
394 return Err("duplicate field: effort".into());
395 }
396 if p.peek_is_null() {
397 p.parse_null()?;
398 effort = None;
399 } else {
400 let v = p
401 .parse_simple_str()
402 .map_err(|err| format!("Parsing effort: {err}"))?;
403 let e = if v.eq_ignore_ascii_case("none") {
404 ReasoningEffort::None
405 } else if v.eq_ignore_ascii_case("low") {
406 ReasoningEffort::Low
407 } else if v.eq_ignore_ascii_case("medium") {
408 ReasoningEffort::Medium
409 } else if v.eq_ignore_ascii_case("high") {
410 ReasoningEffort::High
411 } else if v.eq_ignore_ascii_case("xhigh") {
412 ReasoningEffort::XHigh
413 } else {
414 return Err("invalid effort".into());
415 };
416 effort = Some(e);
417 }
418 }
419 "tokens" => {
420 if tokens.is_some() {
421 return Err("duplicate field: tokens".into());
422 }
423 if p.peek_is_null() {
424 p.parse_null()?;
425 tokens = None;
426 } else {
427 tokens = Some(p.parse_u32()?);
428 }
429 }
430 _ => return Err("unknown field".into()),
431 }
432
433 p.skip_ws();
434 if p.try_consume(b',') {
435 continue;
436 }
437
438 p.skip_ws();
439 if p.try_consume(b'}') {
440 break;
441 }
442
443 if !p.eof() {
445 return Err("expected ',' or '}'".into());
446 } else {
447 return Err("unexpected end of input".into());
448 }
449 }
450
451 p.skip_ws();
452 if !p.eof() {
453 return Err("trailing characters after JSON object".into());
454 }
455
456 let enabled = enabled.ok_or("missing required field: enabled")?;
457
458 Ok(ReasoningConfig {
459 enabled,
460 effort,
461 tokens,
462 })
463 }
464}
465
466impl PromptOpts {
467 pub fn from_json(input: &str) -> Result<Self, Cow<'static, str>> {
468 let mut p = Parser::new(input);
469
470 p.skip_ws();
471 p.expect(b'{')?;
472
473 let mut prompt: Option<String> = None;
474 let mut model: Option<String> = None;
475 let mut provider: Option<String> = None;
476 let mut system: Option<String> = None;
477 let mut priority: Option<Priority> = None;
478 let mut reasoning: Option<ReasoningConfig> = None;
479 let mut show_reasoning: Option<bool> = None;
480 let mut quiet: Option<bool> = None;
481 let mut merge_config = true;
482
483 p.skip_ws();
484 if p.try_consume(b'}') {
485 return Ok(PromptOpts {
486 prompt,
487 models: vec![],
488 provider,
489 system,
490 priority,
491 reasoning,
492 show_reasoning,
493 quiet,
494 merge_config,
495 });
496 }
497
498 loop {
499 p.skip_ws();
500 let key = p.parse_simple_str()?;
501 p.skip_ws();
502 p.expect(b':')?;
503 p.skip_ws();
504
505 match key {
506 "prompt" => {
507 prompt = p.parse_opt_string()?;
508 }
509 "model" => {
510 model = p.parse_opt_string()?;
511 }
512 "provider" => {
513 provider = p.parse_opt_string()?;
514 }
515 "system" => {
516 system = p.parse_opt_string()?;
517 }
518 "priority" => {
519 if p.peek_is_null() {
520 p.parse_null()?;
521 priority = None;
522 } else {
523 let s = p.parse_simple_str()?;
524 priority = Some(Priority::from_str(s).map_err(|_| "invalid priority")?);
525 }
526 }
527 "reasoning" => {
528 if p.peek_is_null() {
529 p.parse_null()?;
530 reasoning = None;
531 } else {
532 let slice = p.value_slice()?; let cfg = ReasoningConfig::from_json(slice).map_err(|e| {
535 format!("parser::PromptOpts::from_json {e}, invalid reasoning")
536 })?;
537 reasoning = Some(cfg);
538 }
539 }
540 "show_reasoning" => {
541 if p.peek_is_null() {
542 p.parse_null()?;
543 show_reasoning = None;
544 } else {
545 show_reasoning = Some(p.parse_bool()?);
546 }
547 }
548 "quiet" => {
549 if p.peek_is_null() {
550 p.parse_null()?;
551 quiet = None;
552 } else {
553 quiet = Some(p.parse_bool()?);
554 }
555 }
556 "merge_config" => {
557 if p.peek_is_null() {
558 p.parse_null()?;
559 merge_config = true;
560 } else {
561 merge_config = p.parse_bool()?;
562 }
563 }
564 _ => {
565 p.skip_value()?;
567 }
568 }
569
570 p.skip_ws();
571 if p.try_consume(b',') {
572 continue;
573 } else {
574 p.expect(b'}')?;
575 break;
576 }
577 }
578
579 Ok(PromptOpts {
580 prompt,
581 models: model.map(|m| vec![m]).unwrap_or_default(),
582 provider,
583 system,
584 priority,
585 reasoning,
586 show_reasoning,
587 quiet,
588 merge_config,
589 })
590 }
591}
592
593impl config::ConfigFile {
594 pub fn from_json(json: &str) -> Result<Self, Cow<'static, str>> {
595 let mut p = Parser::new(json);
596 p.skip_ws();
597 p.expect(b'{')?;
598
599 let mut settings: Option<config::Settings> = None;
600 let mut keys: Vec<config::ApiKey> = vec![];
601 let mut prompt_opts: Option<PromptOpts> = None;
602
603 loop {
604 p.skip_ws();
605 if p.try_consume(b'}') {
606 break;
607 }
608
609 let key = p
610 .parse_simple_str()
611 .map_err(|err| format!("ConfigFile parsing key: {err}"))?;
612 p.skip_ws();
613 p.expect(b':')?;
614 p.skip_ws();
615
616 match key {
617 "settings" => {
618 if settings.is_some() {
619 return Err("duplicate field: settings".into());
620 }
621 let settings_json = p.value_slice()?;
622 settings = Some(config::Settings::from_json(settings_json)?);
623 }
624 "keys" => {
625 if !keys.is_empty() {
626 return Err("duplicate field: keys".into());
627 }
628 if !p.try_consume(b'[') {
629 return Err("keys: Expected array".into());
630 }
631 loop {
632 let j = p.value_slice()?;
633 let api_key = config::ApiKey::from_json(j)?;
634 keys.push(api_key);
635 p.skip_ws();
636 if p.try_consume(b',') {
637 continue;
638 }
639 p.skip_ws();
640 if p.try_consume(b']') {
641 break;
642 }
643 }
644 }
645 "prompt_opts" => {
646 if prompt_opts.is_some() {
647 return Err("duplicate field: prompt_opts".into());
648 }
649 let opts_json = p.value_slice()?;
650 prompt_opts = Some(PromptOpts::from_json(opts_json)?);
651 }
652 _ => return Err("unknown field".into()),
653 }
654 p.skip_ws();
655 if p.try_consume(b',') {
656 continue;
657 }
658 p.skip_ws();
659 if p.try_consume(b'}') {
660 break;
661 }
662 }
663
664 Ok(config::ConfigFile {
665 settings,
666 keys,
667 prompt_opts,
668 })
669 }
670}
671
672impl config::Settings {
673 pub fn from_json(json: &str) -> Result<Self, Cow<'static, str>> {
674 let mut p = Parser::new(json);
675 p.skip_ws();
676 p.expect(b'{')?;
677
678 let mut save_to_file = None;
679 let mut dns = vec![];
680
681 loop {
682 p.skip_ws();
683 if p.try_consume(b'}') {
684 break;
685 }
686
687 let key = p
688 .parse_simple_str()
689 .map_err(|err| format!("Settings parsing key: {err}"))?;
690 p.skip_ws();
691 p.expect(b':')?;
692 p.skip_ws();
693
694 match key {
695 "save_to_file" => {
696 if save_to_file.is_some() {
697 return Err("duplicate field: save_to_file".into());
698 }
699 if p.peek_is_null() {
700 p.parse_null()?;
701 save_to_file = None;
702 } else {
703 save_to_file = Some(p.parse_bool()?);
704 }
705 }
706 "dns" => {
707 if !dns.is_empty() {
708 return Err("duplicate field: dns".into());
709 }
710 if !p.try_consume(b'[') {
711 return Err("dns: Expected array".into());
712 }
713 loop {
714 let addr = p.parse_string()?;
715 dns.push(addr);
716 p.skip_ws();
717 if p.try_consume(b',') {
718 continue;
719 }
720 p.skip_ws();
721 if p.try_consume(b']') {
722 break;
723 }
724 }
725 }
726 _ => return Err("unknown field".into()),
727 }
728
729 p.skip_ws();
730 if p.try_consume(b',') {
731 continue;
732 }
733 p.skip_ws();
734 if p.try_consume(b'}') {
735 break;
736 }
737
738 if !p.eof() {
740 return Err("expected ',' or '}'".into());
741 } else {
742 return Err("unexpected end of input".into());
743 }
744 }
745
746 let default = config::Settings::default();
747 Ok(config::Settings {
748 save_to_file: save_to_file.unwrap_or(default.save_to_file),
749 dns,
750 })
751 }
752}
753
754impl config::ApiKey {
755 pub fn from_json(json: &str) -> Result<Self, Cow<'static, str>> {
756 let mut p = Parser::new(json);
757 p.skip_ws();
758 p.expect(b'{')?;
759
760 let mut name = None;
761 let mut value = None;
762
763 loop {
764 p.skip_ws();
765 if p.try_consume(b'}') {
766 break;
767 }
768
769 let key = p
770 .parse_simple_str()
771 .map_err(|err| format!("ApiKey parsing key: {err}"))?;
772 p.skip_ws();
773 p.expect(b':')?;
774 p.skip_ws();
775
776 match key {
777 "name" => {
778 if name.is_some() {
779 return Err("duplicate field: name".into());
780 }
781 name = Some(
782 p.parse_string()
783 .map_err(|err| format!("Parsing name: {err}"))?,
784 );
785 }
786 "value" => {
787 if value.is_some() {
788 return Err("duplicate field: value".into());
789 }
790 value = Some(
791 p.parse_string()
792 .map_err(|err| format!("Parsing name: {err}"))?,
793 );
794 }
795 _ => return Err("unknown field".into()),
796 }
797 p.skip_ws();
798 if p.try_consume(b',') {
799 continue;
800 } else {
801 p.expect(b'}')?;
802 break;
803 }
804 }
805
806 Ok(config::ApiKey::new(
807 name.expect("Missing ApiKey name"),
808 value.expect("Missing ApiKey value"),
809 ))
810 }
811}
812
813struct Parser<'a> {
817 s: &'a str,
818 b: &'a [u8],
819 i: usize,
820}
821
822impl<'a> Parser<'a> {
823 fn new(s: &'a str) -> Self {
824 Self {
825 s,
826 b: s.as_bytes(),
827 i: 0,
828 }
829 }
830
831 fn eof(&self) -> bool {
832 self.i >= self.b.len()
833 }
834
835 fn peek(&self) -> Option<u8> {
836 if self.eof() {
837 None
838 } else {
839 Some(self.b[self.i])
840 }
841 }
842
843 fn try_consume(&mut self, ch: u8) -> bool {
844 if self.peek() == Some(ch) {
845 self.i += 1;
846 true
847 } else {
848 false
849 }
850 }
851
852 fn expect(&mut self, ch: u8) -> Result<(), &'static str> {
853 if self.try_consume(ch) {
854 Ok(())
855 } else {
856 Err("expected character")
857 }
858 }
859
860 fn skip_ws(&mut self) {
861 while let Some(c) = self.peek() {
862 match c {
863 b' ' | b'\n' | b'\r' | b'\t' => self.i += 1,
864 _ => break,
865 }
866 }
867 }
868
869 fn starts_with_bytes(&self, pat: &[u8]) -> bool {
870 let end = self.i + pat.len();
871 end <= self.b.len() && &self.b[self.i..end] == pat
872 }
873
874 fn parse_null(&mut self) -> Result<(), &'static str> {
875 if self.starts_with_bytes(b"null") {
876 self.i += 4;
877 Ok(())
878 } else {
879 Err("expected null")
880 }
881 }
882
883 fn peek_is_null(&self) -> bool {
884 self.starts_with_bytes(b"null")
885 }
886
887 fn parse_bool(&mut self) -> Result<bool, String> {
888 self.skip_ws();
889 if self.starts_with_bytes(b"true") {
890 self.i += 4;
891 Ok(true)
892 } else if self.starts_with_bytes(b"false") {
893 self.i += 5;
894 Ok(false)
895 } else {
896 Err(format!(
897 "Expected boolean, got '{}'",
898 String::from_utf8_lossy(&self.b[self.i..])
899 ))
900 }
901 }
902
903 fn parse_u32(&mut self) -> Result<u32, &'static str> {
904 self.skip_ws();
905 if self.eof() {
906 return Err("expected number");
907 }
908 if self.peek() == Some(b'-') {
909 return Err("negative not allowed");
910 }
911 let mut val: u32 = 0;
912 let mut read_any = false;
913 let len = self.b.len();
914 while self.i < len {
915 let c = self.b[self.i];
916 if c.is_ascii_digit() {
917 read_any = true;
918 let digit = (c - b'0') as u32;
919 if val > (u32::MAX - digit) / 10 {
921 return Err("u32 overflow");
922 }
923 val = val * 10 + digit;
924 self.i += 1;
925 } else {
926 break;
927 }
928 }
929 if !read_any {
930 return Err("expected integer");
931 }
932 Ok(val)
933 }
934
935 fn parse_f32(&mut self) -> Result<f32, &'static str> {
936 self.skip_ws();
937 if self.eof() {
938 return Err("expected number");
939 }
940
941 let len = self.b.len();
942
943 let mut neg = false;
945 if let Some(c) = self.peek() {
946 if c == b'-' {
947 neg = true;
948 self.i += 1;
949 } else if c == b'+' {
950 self.i += 1;
951 }
952 }
953
954 let mut mant: u32 = 0;
956 let mut mant_digits: i32 = 0;
957 let mut ints: i32 = 0;
958
959 while self.i < len {
961 let c = self.b[self.i];
962 if c.is_ascii_digit() {
963 if mant_digits < 9 {
964 mant = mant.saturating_mul(10).wrapping_add((c - b'0') as u32);
965 mant_digits += 1;
966 }
967 self.i += 1;
968 ints += 1;
969 } else {
970 break;
971 }
972 }
973
974 let mut frac_any = false;
976 if self.peek() == Some(b'.') {
977 self.i += 1;
978 let start_frac = self.i;
979 while self.i < len {
980 let c = self.b[self.i];
981 if c.is_ascii_digit() {
982 if mant_digits < 9 {
983 mant = mant.saturating_mul(10).wrapping_add((c - b'0') as u32);
984 mant_digits += 1;
985 }
986 self.i += 1;
987 } else {
988 break;
989 }
990 }
991 frac_any = self.i > start_frac;
992 }
993
994 if ints == 0 && !frac_any {
995 return Err("expected number");
996 }
997
998 let mut exp_part: i32 = 0;
1000 if let Some(ech) = self.peek()
1001 && (ech == b'e' || ech == b'E')
1002 {
1003 self.i += 1;
1004 let mut eneg = false;
1005 if let Some(signch) = self.peek() {
1006 if signch == b'-' {
1007 eneg = true;
1008 self.i += 1;
1009 } else if signch == b'+' {
1010 self.i += 1;
1011 }
1012 }
1013 if self.eof() || !self.b[self.i].is_ascii_digit() {
1014 return Err("expected exponent");
1015 }
1016 let mut eacc: i32 = 0;
1017 while self.i < len {
1018 let c = self.b[self.i];
1019 if c.is_ascii_digit() {
1020 let d = (c - b'0') as i32;
1021 if eacc < 1_000_000_000 / 10 {
1022 eacc = eacc * 10 + d;
1023 } else {
1024 eacc = 1_000_000_000; }
1026 self.i += 1;
1027 } else {
1028 break;
1029 }
1030 }
1031 exp_part = if eneg { -eacc } else { eacc };
1032 }
1033
1034 let exp10 = ints - mant_digits + exp_part;
1036
1037 let mut val = mant as f64;
1039
1040 const POW10_POS: [f64; 39] = [
1041 1.0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1042 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
1043 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38,
1044 ];
1045 const POW10_NEG: [f64; 46] = [
1046 1.0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-10, 1e-11, 1e-12, 1e-13,
1047 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19, 1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25,
1048 1e-26, 1e-27, 1e-28, 1e-29, 1e-30, 1e-31, 1e-32, 1e-33, 1e-34, 1e-35, 1e-36, 1e-37,
1049 1e-38, 1e-39, 1e-40, 1e-41, 1e-42, 1e-43, 1e-44, 1e-45,
1050 ];
1051
1052 if exp10 > 0 {
1053 let mut e = exp10;
1054 while e > 0 {
1055 let chunk = if e > 38 { 38 } else { e } as usize;
1056 val *= POW10_POS[chunk];
1057 if !val.is_finite() {
1058 return Err("f32 overflow");
1059 }
1060 e -= chunk as i32;
1061 }
1062 } else if exp10 < 0 {
1063 let mut e = -exp10;
1064 while e > 0 {
1065 let chunk = if e > 45 { 45 } else { e } as usize;
1066 val *= POW10_NEG[chunk];
1067 if val == 0.0 {
1068 break;
1069 }
1070 e -= chunk as i32;
1071 }
1072 }
1073
1074 let mut out = val as f32;
1075 if !out.is_finite() {
1076 return Err("f32 overflow");
1077 }
1078 if neg {
1079 out = -out;
1080 }
1081 Ok(out)
1082 }
1083
1084 fn parse_simple_str(&mut self) -> Result<&'a str, &'static str> {
1085 self.skip_ws();
1086 if self.peek() != Some(b'"') {
1087 return Err("expected string");
1088 }
1089 self.i += 1;
1090 let start = self.i;
1091 let len = self.b.len();
1092 while self.i < len {
1093 let c = self.b[self.i];
1094 if c == b'\\' {
1095 return Err("string escapes are not supported");
1097 }
1098 if c == b'"' {
1099 let end = self.i;
1100 self.i += 1; return Ok(&self.s[start..end]);
1104 }
1105 self.i += 1;
1106 }
1107 Err("unterminated string")
1108 }
1109
1110 fn parse_string(&mut self) -> Result<String, Cow<'static, str>> {
1111 self.skip_ws();
1112 if self.peek() != Some(b'"') {
1113 return Err(format!(
1114 "expected string got: '{}'",
1115 String::from_utf8_lossy(&self.b[self.i..])
1116 )
1117 .into());
1118 }
1119 let start = self.i + 1;
1120 let mut i = start;
1121 let len = self.b.len();
1122
1123 let mut needs_unescape = false;
1125 while i < len {
1126 let c = self.b[i];
1127 if c == b'\\' {
1128 needs_unescape = true;
1129 break;
1130 }
1131 if c == b'"' {
1132 let s = core::str::from_utf8(&self.b[start..i]).map_err(|_| "utf8 error")?;
1134 self.i = i + 1;
1135 return Ok(s.to_owned());
1136 }
1137 i += 1;
1138 }
1139 if !needs_unescape {
1140 return Err("unterminated string".into());
1141 }
1142
1143 let mut out = String::with_capacity((i - start) + 16);
1145 let mut seg_start = start;
1146 while i < len {
1147 let c = self.b[i];
1148 if c == b'\\' {
1149 if i > seg_start {
1151 out.push_str(
1152 core::str::from_utf8(&self.b[seg_start..i]).map_err(|_| "utf8 error")?,
1153 );
1154 }
1155 i += 1;
1156 if i >= len {
1157 return Err("bad escape".into());
1158 }
1159 let e = self.b[i];
1160 match e {
1161 b'"' => out.push('"'),
1162 b'\\' => out.push('\\'),
1163 b'/' => out.push('/'),
1164 b'b' => out.push('\u{0008}'),
1165 b'f' => out.push('\u{000C}'),
1166 b'n' => out.push('\n'),
1167 b'r' => out.push('\r'),
1168 b't' => out.push('\t'),
1169 b'u' => {
1170 let (cp, new_i) = self.parse_u_escape(i + 1)?;
1171 i = new_i - 1; if let Some(ch) = core::char::from_u32(cp) {
1173 out.push(ch);
1174 } else {
1175 return Err("invalid unicode".into());
1176 }
1177 }
1178 _ => return Err("bad escape".into()),
1179 }
1180 i += 1;
1181 seg_start = i;
1182 continue;
1183 } else if c == b'"' {
1184 if i > seg_start {
1186 out.push_str(
1187 core::str::from_utf8(&self.b[seg_start..i]).map_err(|_| "utf8 error")?,
1188 );
1189 }
1190 self.i = i + 1;
1191 return Ok(out);
1192 } else {
1193 i += 1;
1194 }
1195 }
1196 Err("unterminated string".into())
1197 }
1198
1199 fn parse_u_escape(&self, i: usize) -> Result<(u32, usize), &'static str> {
1201 fn hex4(bytes: &[u8], i: usize) -> Result<(u16, usize), &'static str> {
1202 let end = i + 4;
1203 if end > bytes.len() {
1204 return Err("short \\u");
1205 }
1206 let mut v: u16 = 0;
1207 for b in bytes.iter().take(end).skip(i) {
1208 v = (v << 4) | hex_val(*b)?;
1209 }
1210 Ok((v, end))
1211 }
1212 fn hex_val(b: u8) -> Result<u16, &'static str> {
1213 match b {
1214 b'0'..=b'9' => Ok((b - b'0') as u16),
1215 b'a'..=b'f' => Ok((b - b'a' + 10) as u16),
1216 b'A'..=b'F' => Ok((b - b'A' + 10) as u16),
1217 _ => Err("bad hex"),
1218 }
1219 }
1220
1221 let (first, i2) = hex4(self.b, i)?;
1222 let cp = first as u32;
1223
1224 if (0xD800..=0xDBFF).contains(&first) {
1226 if i2 + 2 > self.b.len() || self.b[i2] != b'\\' || self.b[i2 + 1] != b'u' {
1228 return Err("missing low surrogate");
1229 }
1230 let (second, i3) = hex4(self.b, i2 + 2)?;
1231 if !(0xDC00..=0xDFFF).contains(&second) {
1232 return Err("invalid low surrogate");
1233 }
1234 let high = (first as u32) - 0xD800;
1235 let low = (second as u32) - 0xDC00;
1236 let code = 0x10000 + ((high << 10) | low);
1237 Ok((code, i3))
1238 } else if (0xDC00..=0xDFFF).contains(&first) {
1239 Err("unpaired low surrogate")
1240 } else {
1241 Ok((cp, i2))
1242 }
1243 }
1244
1245 fn parse_opt_string(&mut self) -> Result<Option<String>, Cow<'static, str>> {
1246 if self.peek_is_null() {
1247 self.parse_null()?;
1248 Ok(None)
1249 } else {
1250 let s = self.parse_string()?;
1251 Ok(Some(s))
1252 }
1253 }
1254
1255 fn value_slice(&mut self) -> Result<&'a str, &'static str> {
1257 self.skip_ws();
1258 let start = self.i;
1259 let end = self.find_value_end()?;
1260 let out = &self.s[start..end];
1261 self.i = end;
1262 Ok(out)
1263 }
1264
1265 fn skip_value(&mut self) -> Result<(), &'static str> {
1267 let _ = self.value_slice()?;
1268 Ok(())
1269 }
1270
1271 fn find_value_end(&mut self) -> Result<usize, &'static str> {
1272 if self.eof() {
1273 return Err("unexpected end");
1274 }
1275 match self.b[self.i] {
1276 b'"' => self.scan_string_end(),
1277 b'{' => self.scan_brace_block(b'{', b'}'),
1278 b'[' => self.scan_brace_block(b'[', b']'),
1279 b't' => {
1280 if self.starts_with_bytes(b"true") {
1281 Ok(self.i + 4)
1282 } else {
1283 Err("bad literal")
1284 }
1285 }
1286 b'f' => {
1287 if self.starts_with_bytes(b"false") {
1288 Ok(self.i + 5)
1289 } else {
1290 Err("bad literal")
1291 }
1292 }
1293 b'n' => {
1294 if self.starts_with_bytes(b"null") {
1295 Ok(self.i + 4)
1296 } else {
1297 Err("bad literal")
1298 }
1299 }
1300 b'-' | b'0'..=b'9' => self.scan_number_end(),
1301 _ => Err("unexpected token"),
1302 }
1303 }
1304
1305 fn scan_string_end(&self) -> Result<usize, &'static str> {
1306 let mut i = self.i + 1;
1307 let len = self.b.len();
1308 let mut escaped = false;
1309 while i < len {
1310 let c = self.b[i];
1311 if escaped {
1312 escaped = false;
1313 i += 1;
1314 continue;
1315 }
1316 if c == b'\\' {
1317 escaped = true;
1318 i += 1;
1319 continue;
1320 }
1321 if c == b'"' {
1322 return Ok(i + 1);
1323 }
1324 i += 1;
1325 }
1326 Err("unterminated string")
1327 }
1328
1329 fn scan_brace_block(&self, open: u8, close: u8) -> Result<usize, &'static str> {
1330 let mut i = self.i;
1331 let len = self.b.len();
1332 let mut depth = 0usize;
1333 while i < len {
1334 let c = self.b[i];
1335 if c == b'"' {
1336 let p = Parser {
1338 s: self.s,
1339 b: self.b,
1340 i,
1341 };
1342 i = p.scan_string_end()?; continue;
1344 }
1345 if c == open {
1346 depth += 1;
1347 } else if c == close {
1348 depth -= 1;
1349 if depth == 0 {
1350 return Ok(i + 1);
1351 }
1352 }
1353 i += 1;
1354 }
1355 Err("unterminated structure")
1356 }
1357
1358 fn scan_number_end(&self) -> Result<usize, &'static str> {
1359 let len = self.b.len();
1360 let mut i = self.i;
1361
1362 if self.b[i] == b'-' {
1363 i += 1;
1364 if i >= len {
1365 return Err("bad number");
1366 }
1367 }
1368
1369 match self.b[i] {
1371 b'0' => {
1372 i += 1;
1373 }
1374 b'1'..=b'9' => {
1375 i += 1;
1376 while i < len {
1377 match self.b[i] {
1378 b'0'..=b'9' => i += 1,
1379 _ => break,
1380 }
1381 }
1382 }
1383 _ => return Err("bad number"),
1384 }
1385
1386 if i < len && self.b[i] == b'.' {
1388 i += 1;
1389 if i >= len || !self.b[i].is_ascii_digit() {
1390 return Err("bad number");
1391 }
1392 while i < len && self.b[i].is_ascii_digit() {
1393 i += 1;
1394 }
1395 }
1396
1397 if i < len && (self.b[i] == b'e' || self.b[i] == b'E') {
1399 i += 1;
1400 if i < len && (self.b[i] == b'+' || self.b[i] == b'-') {
1401 i += 1;
1402 }
1403 if i >= len || !self.b[i].is_ascii_digit() {
1404 return Err("bad number");
1405 }
1406 while i < len && self.b[i].is_ascii_digit() {
1407 i += 1;
1408 }
1409 }
1410
1411 Ok(i)
1412 }
1413}
1414
1415#[cfg(test)]
1416mod tests {
1417 extern crate alloc;
1418 use alloc::string::ToString;
1419
1420 use super::*;
1421 use crate::LastData;
1422
1423 #[test]
1424 fn rp1() {
1425 let cfg = ReasoningConfig::from_json(r#"{"enabled": false}"#).unwrap();
1426 assert!(!cfg.enabled);
1427 assert!(cfg.effort.is_none());
1428 assert!(cfg.tokens.is_none());
1429 }
1430
1431 #[test]
1432 fn rp2() {
1433 let cfg = ReasoningConfig::from_json(r#"{"enabled": true, "effort": "medium"}"#).unwrap();
1434 assert!(cfg.enabled);
1435 assert_eq!(cfg.effort, Some(ReasoningEffort::Medium));
1436 assert!(cfg.tokens.is_none());
1437 }
1438
1439 #[test]
1440 fn rp3() {
1441 let cfg = ReasoningConfig::from_json(r#"{"enabled": true, "tokens": 2048}"#).unwrap();
1442 assert!(cfg.enabled);
1443 assert_eq!(cfg.tokens, Some(2048));
1444 assert!(cfg.effort.is_none());
1445 }
1446
1447 #[test]
1448 fn rp4() {
1449 let cfg = ReasoningConfig::from_json(r#"{"enabled":true,"effort":"high","tokens":null}"#)
1450 .unwrap();
1451 assert!(cfg.enabled);
1452 assert_eq!(cfg.effort, Some(ReasoningEffort::High));
1453 assert!(cfg.tokens.is_none());
1454 }
1455
1456 #[test]
1457 fn cpo1() {
1458 let s = r#"
1459 {
1460 "prompt": "\n\nExample JSON 1: {\"enabled\": false}\n",
1461 "model": "google/gemma-3n-e4b-it:free",
1462 "system": "Make your answer concise but complete. No yapping. Direct professional tone. No emoji.",
1463 "show_reasoning": false,
1464 "reasoning": { "enabled": false },
1465 "merge_config": true
1466 }
1467 "#;
1468 let opts = PromptOpts::from_json(s).unwrap();
1469 assert!(!opts.show_reasoning.unwrap());
1470 assert_eq!(opts.models, vec!["google/gemma-3n-e4b-it:free"]);
1471 assert!(!opts.reasoning.unwrap().enabled);
1472 assert!(opts.merge_config);
1473 }
1474
1475 #[test]
1476 fn cpo2() {
1477 let s = r#"
1478 {"model":"openai/gpt-5","provider":"openai","system":"Make your answer concise but complete. No yapping. Direct professional tone. No emoji.","priority":null,"reasoning":{"enabled":true,"effort":"high","tokens":null},"show_reasoning":false,"quiet":true}
1479 "#;
1480 let opts = PromptOpts::from_json(s).unwrap();
1481 assert!(!opts.show_reasoning.unwrap());
1482 assert_eq!(opts.models, vec!["openai/gpt-5"]);
1483 assert!(opts.reasoning.as_ref().unwrap().enabled);
1484 assert_eq!(
1485 opts.reasoning.as_ref().unwrap().effort,
1486 Some(ReasoningEffort::High)
1487 );
1488 }
1489
1490 #[test]
1491 fn last_data() {
1492 let s = r#"
1493{"opts":{"model":"google/gemma-3n-e4b-it:free","provider":"google-ai-studio","system":"Make your answer concise but complete. No yapping. Direct professional tone. No emoji.","priority":null,"reasoning":{"enabled":false,"effort":null,"tokens":null},"show_reasoning":false},"messages":[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hello there! 😊How can I help you today? I'm ready for anything – questions, stories, ideas, or just a friendly chat!Let me know what's on your mind. ✨"}]}
1494"#;
1495 let l = LastData::from_json(s).unwrap();
1496 assert_eq!(l.opts.provider.as_deref(), Some("google-ai-studio"));
1497 assert_eq!(l.messages.len(), 2);
1498 }
1499
1500 #[test]
1501 fn test_usage() {
1502 let s = r#"{"prompt_tokens":42,"completion_tokens":2,"total_tokens":44,"cost":0.0534,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0,"upstream_inference_completions_cost":0},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}"#;
1503 let usage = Usage::from_json(s).unwrap();
1504 assert_eq!(usage.cost, 0.0534);
1505 }
1506
1507 #[test]
1508 fn test_choice() {
1509 let s = r#"{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}"#;
1510 let choice = Choice::from_json(s).unwrap();
1511 assert_eq!(choice.delta.content.as_deref(), Some("Hello"));
1512 }
1513
1514 #[test]
1515 fn test_chat_completions_response_simple() {
1516 let arr = [
1517 r#"{"id":"gen-1756743299-7ytIBcjALWQQShwMQfw9","provider":"Meta","model":"meta-llama/llama-3.3-8b-instruct:free","object":"chat.completion.chunk","created":1756743300,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}"#,
1518 r#"{"id":"gen-1756743299-7ytIBcjALWQQShwMQfw9","provider":"Meta","model":"meta-llama/llama-3.3-8b-instruct:free","object":"chat.completion.chunk","created":1756743300,"choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}"#,
1519 r#"{"id":"gen-1756743299-7ytIBcjALWQQShwMQfw9","provider":"Meta","model":"meta-llama/llama-3.3-8b-instruct:free","object":"chat.completion.chunk","created":1756743300,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":42,"completion_tokens":2,"total_tokens":44,"cost":0,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0,"upstream_inference_completions_cost":0},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}"#,
1520 ];
1521 for a in arr {
1522 let ccr = ChatCompletionsResponse::from_json(a).unwrap();
1523 assert_eq!(ccr.provider.as_deref(), Some("Meta"));
1524 assert_eq!(
1525 ccr.model.as_deref(),
1526 Some("meta-llama/llama-3.3-8b-instruct:free")
1527 );
1528 assert_eq!(ccr.choices.len(), 1);
1529 }
1530 }
1531
1532 #[test]
1533 fn test_chat_completions_response_more() {
1534 let arr = [
1535 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":"","reasoning":null,"reasoning_details":[]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}"#,
1536 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":"","reasoning":null,"reasoning_details":[]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}"#,
1537 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":"","reasoning":null,"reasoning_details":[]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}"#,
1538 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":"","reasoning":null,"reasoning_details":[]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}"#,
1539 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":"","reasoning":null,"reasoning_details":[]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}"#,
1540 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":"Rea","reasoning":null,"reasoning_details":[]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}"#,
1541 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":"l","reasoning":null,"reasoning_details":[]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}"#,
1542 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":" Madrid, 14 times.","reasoning":null,"reasoning_details":[]},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}"#,
1543 r#"{"id":"gen-1756749262-liysSWPMM37eb25U5gXO","provider":"WandB","model":"deepseek/deepseek-chat-v3.1","object":"chat.completion.chunk","created":1756749262,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":33,"completion_tokens":8,"total_tokens":41,"cost":0.0000310365,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00001815,"upstream_inference_completions_cost":0.0000132},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}"#,
1544 ];
1545 for a in arr {
1546 let ccr = ChatCompletionsResponse::from_json(a).unwrap();
1547 assert_eq!(ccr.provider.as_deref(), Some("WandB"));
1548 assert_eq!(ccr.model.as_deref(), Some("deepseek/deepseek-chat-v3.1"));
1549 assert_eq!(ccr.choices.len(), 1);
1550 }
1551 }
1552
1553 #[test]
1554 fn api_key() {
1555 let s = r#"{"name":"openrouter","value":"sk-or-v1-a123b456c789d012a345b8032470394876576573242374098174093274abcdef"}"#;
1556 let got = config::ApiKey::from_json(s).unwrap();
1557 let expect = config::ApiKey::new(
1558 "openrouter".to_string(),
1559 "sk-or-v1-a123b456c789d012a345b8032470394876576573242374098174093274abcdef".to_string(),
1560 );
1561 assert_eq!(got, expect);
1562 }
1563
1564 #[test]
1565 fn settings() {
1566 let s = r#"{
1567 "save_to_file": true,
1568 "dns": ["104.18.2.115", "104.18.3.115"]
1569}"#;
1570 let settings = config::Settings::from_json(s).unwrap();
1571 assert!(settings.save_to_file);
1572 assert_eq!(settings.dns, ["104.18.2.115", "104.18.3.115"]);
1573 }
1574
1575 #[test]
1576 fn config_file() {
1577 let s = r#"
1578{
1579 "keys": [{"name": "openrouter", "value": "sk-or-v1-abcd1234"}],
1580 "settings": {
1581 "save_to_file": true,
1582 "dns": ["104.18.2.115", "104.18.3.115"]
1583 },
1584 "prompt_opts": {
1585 "model": "google/gemma-3n-e4b-it:free",
1586 "system": "Make your answer concise but complete. No yapping. Direct professional tone. No emoji.",
1587 "quiet": false,
1588 "show_reasoning": false,
1589 "reasoning": {
1590 "enabled": false
1591 }
1592 }
1593}
1594"#;
1595 let cfg = config::ConfigFile::from_json(s).unwrap();
1596 assert_eq!(cfg.keys.len(), 1);
1597 assert!(cfg.settings.is_some());
1598 assert!(cfg.prompt_opts.is_some());
1599 }
1600}