xberg 1.2.3 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  // This file is auto-generated by alef. DO NOT EDIT.
2
- // alef:hash:df82e1e6fdb3011e4f34e04b6e19d4d2b79d9c65cef93cd9d444d13ad8bcd155
2
+ // alef:hash:f96e7fa37ad41175262a0e7e7e6cc03473ea5e795d7cf76e5118687cbeff2554
3
3
  // Re-generate with: alef generate
4
4
  #![allow(dead_code, unused_imports, unused_variables)]
5
5
  #![allow(
@@ -6006,8 +6006,12 @@ impl DbfMetadata {
6006
6006
  #[magnus::wrap(class = "Xberg::DeepseekOcrBackendOptions")]
6007
6007
  pub struct DeepseekOcrBackendOptions {
6008
6008
  model_path: Option<String>,
6009
+ model_id: Option<String>,
6010
+ hf_revision: Option<String>,
6011
+ cache_dir: Option<String>,
6009
6012
  device: Option<CandleDevicePreference>,
6010
6013
  version: Option<u32>,
6014
+ dtype: Option<CandleDeepseekOcrDtype>,
6011
6015
  }
6012
6016
 
6013
6017
  #[cfg(feature = "candle-ocr")]
@@ -6074,6 +6078,33 @@ impl DeepseekOcrBackendOptions {
6074
6078
  })?),
6075
6079
  None => None,
6076
6080
  },
6081
+ model_id: match kwargs.get(ruby.to_symbol("model_id")).filter(|v| !v.is_nil()) {
6082
+ Some(v) => Some(String::try_convert(v).map_err(|e| {
6083
+ magnus::Error::new(
6084
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
6085
+ format!("invalid value for `model_id`: {}", e),
6086
+ )
6087
+ })?),
6088
+ None => None,
6089
+ },
6090
+ hf_revision: match kwargs.get(ruby.to_symbol("hf_revision")).filter(|v| !v.is_nil()) {
6091
+ Some(v) => Some(String::try_convert(v).map_err(|e| {
6092
+ magnus::Error::new(
6093
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
6094
+ format!("invalid value for `hf_revision`: {}", e),
6095
+ )
6096
+ })?),
6097
+ None => None,
6098
+ },
6099
+ cache_dir: match kwargs.get(ruby.to_symbol("cache_dir")).filter(|v| !v.is_nil()) {
6100
+ Some(v) => Some(String::try_convert(v).map_err(|e| {
6101
+ magnus::Error::new(
6102
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
6103
+ format!("invalid value for `cache_dir`: {}", e),
6104
+ )
6105
+ })?),
6106
+ None => None,
6107
+ },
6077
6108
  device: match kwargs.get(ruby.to_symbol("device")).filter(|v| !v.is_nil()) {
6078
6109
  Some(v) => Some(CandleDevicePreference::try_convert(v).map_err(|e| {
6079
6110
  magnus::Error::new(
@@ -6092,6 +6123,15 @@ impl DeepseekOcrBackendOptions {
6092
6123
  })?),
6093
6124
  None => None,
6094
6125
  },
6126
+ dtype: match kwargs.get(ruby.to_symbol("dtype")).filter(|v| !v.is_nil()) {
6127
+ Some(v) => Some(CandleDeepseekOcrDtype::try_convert(v).map_err(|e| {
6128
+ magnus::Error::new(
6129
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
6130
+ format!("invalid value for `dtype`: {}", e),
6131
+ )
6132
+ })?),
6133
+ None => None,
6134
+ },
6095
6135
  })
6096
6136
  }
6097
6137
 
@@ -6099,6 +6139,18 @@ impl DeepseekOcrBackendOptions {
6099
6139
  self.model_path.clone()
6100
6140
  }
6101
6141
 
6142
+ fn model_id(&self) -> Option<String> {
6143
+ self.model_id.clone()
6144
+ }
6145
+
6146
+ fn hf_revision(&self) -> Option<String> {
6147
+ self.hf_revision.clone()
6148
+ }
6149
+
6150
+ fn cache_dir(&self) -> Option<String> {
6151
+ self.cache_dir.clone()
6152
+ }
6153
+
6102
6154
  fn device(&self) -> Option<CandleDevicePreference> {
6103
6155
  self.device.clone()
6104
6156
  }
@@ -6106,6 +6158,10 @@ impl DeepseekOcrBackendOptions {
6106
6158
  fn version(&self) -> Option<u32> {
6107
6159
  self.version
6108
6160
  }
6161
+
6162
+ fn dtype(&self) -> Option<CandleDeepseekOcrDtype> {
6163
+ self.dtype.clone()
6164
+ }
6109
6165
  }
6110
6166
 
6111
6167
  #[cfg(feature = "api")]
@@ -12691,6 +12747,202 @@ impl ExtractionConfig {
12691
12747
  core_self.needs_image_data()
12692
12748
  }
12693
12749
 
12750
+ fn runs_ocr_on_embedded_images(&self) -> bool {
12751
+ #[allow(clippy::needless_update)]
12752
+ let core_self = xberg::ExtractionConfig {
12753
+ mime_detection_policy: self.mime_detection_policy.clone().into(),
12754
+
12755
+ use_cache: self.use_cache,
12756
+
12757
+ enable_quality_processing: self.enable_quality_processing,
12758
+
12759
+ ocr: self.ocr.clone().map(Into::into),
12760
+
12761
+ force_ocr: self.force_ocr,
12762
+
12763
+ ocr_strategy: self.ocr_strategy.clone().into(),
12764
+
12765
+ force_ocr_pages: self.force_ocr_pages.clone(),
12766
+
12767
+ disable_ocr: self.disable_ocr,
12768
+
12769
+ chunking: self.chunking.clone().map(Into::into),
12770
+
12771
+ content_filter: self.content_filter.clone().map(Into::into),
12772
+
12773
+ images: self.images.clone().map(Into::into),
12774
+
12775
+ token_reduction: self.token_reduction.clone().map(Into::into),
12776
+
12777
+ language_detection: self.language_detection.clone().map(Into::into),
12778
+
12779
+ pages: self.pages.clone().map(Into::into),
12780
+
12781
+ postprocessor: self.postprocessor.clone().map(Into::into),
12782
+
12783
+ extraction_timeout_secs: self.extraction_timeout_secs,
12784
+
12785
+ max_concurrent_extractions: self.max_concurrent_extractions,
12786
+
12787
+ result_format: self.result_format.clone().into(),
12788
+
12789
+ security_limits: self.security_limits.clone().map(Into::into),
12790
+
12791
+ max_embedded_file_bytes: self.max_embedded_file_bytes,
12792
+
12793
+ output_format: self.output_format.clone().into(),
12794
+
12795
+ escape_markdown: self.escape_markdown,
12796
+
12797
+ table_anchors: self.table_anchors,
12798
+
12799
+ jupyter_cell_rendering: self.jupyter_cell_rendering.clone().into(),
12800
+
12801
+ apply_notebook_cell_tags: self.apply_notebook_cell_tags,
12802
+
12803
+ use_layout_for_markdown: self.use_layout_for_markdown,
12804
+
12805
+ include_document_structure: self.include_document_structure,
12806
+
12807
+ acceleration: self.acceleration.clone().map(Into::into),
12808
+
12809
+ cache_namespace: self.cache_namespace.clone(),
12810
+
12811
+ cache_ttl_secs: self.cache_ttl_secs,
12812
+
12813
+ email: self.email.clone().map(Into::into),
12814
+
12815
+ csv: self.csv.clone().map(Into::into),
12816
+
12817
+ geojson: self.geojson.clone().map(Into::into),
12818
+
12819
+ concurrency: self.concurrency.clone().map(Into::into),
12820
+
12821
+ url: self.url.clone().into(),
12822
+
12823
+ max_archive_depth: self.max_archive_depth,
12824
+
12825
+ structured_extraction: self.structured_extraction.clone().map(Into::into),
12826
+
12827
+ ner: self.ner.clone().map(Into::into),
12828
+
12829
+ redaction: self.redaction.clone().map(Into::into),
12830
+
12831
+ summarization: self.summarization.clone().map(Into::into),
12832
+
12833
+ translation: self.translation.clone().map(Into::into),
12834
+
12835
+ page_classification: self.page_classification.clone().map(Into::into),
12836
+
12837
+ chunk_classification: self.chunk_classification.clone().map(Into::into),
12838
+
12839
+ captioning: self.captioning.clone().map(Into::into),
12840
+
12841
+ qr_codes: self.qr_codes,
12842
+
12843
+ ..Default::default()
12844
+ };
12845
+ core_self.runs_ocr_on_embedded_images()
12846
+ }
12847
+
12848
+ fn wants_own_bytes_in_result(&self) -> bool {
12849
+ #[allow(clippy::needless_update)]
12850
+ let core_self = xberg::ExtractionConfig {
12851
+ mime_detection_policy: self.mime_detection_policy.clone().into(),
12852
+
12853
+ use_cache: self.use_cache,
12854
+
12855
+ enable_quality_processing: self.enable_quality_processing,
12856
+
12857
+ ocr: self.ocr.clone().map(Into::into),
12858
+
12859
+ force_ocr: self.force_ocr,
12860
+
12861
+ ocr_strategy: self.ocr_strategy.clone().into(),
12862
+
12863
+ force_ocr_pages: self.force_ocr_pages.clone(),
12864
+
12865
+ disable_ocr: self.disable_ocr,
12866
+
12867
+ chunking: self.chunking.clone().map(Into::into),
12868
+
12869
+ content_filter: self.content_filter.clone().map(Into::into),
12870
+
12871
+ images: self.images.clone().map(Into::into),
12872
+
12873
+ token_reduction: self.token_reduction.clone().map(Into::into),
12874
+
12875
+ language_detection: self.language_detection.clone().map(Into::into),
12876
+
12877
+ pages: self.pages.clone().map(Into::into),
12878
+
12879
+ postprocessor: self.postprocessor.clone().map(Into::into),
12880
+
12881
+ extraction_timeout_secs: self.extraction_timeout_secs,
12882
+
12883
+ max_concurrent_extractions: self.max_concurrent_extractions,
12884
+
12885
+ result_format: self.result_format.clone().into(),
12886
+
12887
+ security_limits: self.security_limits.clone().map(Into::into),
12888
+
12889
+ max_embedded_file_bytes: self.max_embedded_file_bytes,
12890
+
12891
+ output_format: self.output_format.clone().into(),
12892
+
12893
+ escape_markdown: self.escape_markdown,
12894
+
12895
+ table_anchors: self.table_anchors,
12896
+
12897
+ jupyter_cell_rendering: self.jupyter_cell_rendering.clone().into(),
12898
+
12899
+ apply_notebook_cell_tags: self.apply_notebook_cell_tags,
12900
+
12901
+ use_layout_for_markdown: self.use_layout_for_markdown,
12902
+
12903
+ include_document_structure: self.include_document_structure,
12904
+
12905
+ acceleration: self.acceleration.clone().map(Into::into),
12906
+
12907
+ cache_namespace: self.cache_namespace.clone(),
12908
+
12909
+ cache_ttl_secs: self.cache_ttl_secs,
12910
+
12911
+ email: self.email.clone().map(Into::into),
12912
+
12913
+ csv: self.csv.clone().map(Into::into),
12914
+
12915
+ geojson: self.geojson.clone().map(Into::into),
12916
+
12917
+ concurrency: self.concurrency.clone().map(Into::into),
12918
+
12919
+ url: self.url.clone().into(),
12920
+
12921
+ max_archive_depth: self.max_archive_depth,
12922
+
12923
+ structured_extraction: self.structured_extraction.clone().map(Into::into),
12924
+
12925
+ ner: self.ner.clone().map(Into::into),
12926
+
12927
+ redaction: self.redaction.clone().map(Into::into),
12928
+
12929
+ summarization: self.summarization.clone().map(Into::into),
12930
+
12931
+ translation: self.translation.clone().map(Into::into),
12932
+
12933
+ page_classification: self.page_classification.clone().map(Into::into),
12934
+
12935
+ chunk_classification: self.chunk_classification.clone().map(Into::into),
12936
+
12937
+ captioning: self.captioning.clone().map(Into::into),
12938
+
12939
+ qr_codes: self.qr_codes,
12940
+
12941
+ ..Default::default()
12942
+ };
12943
+ core_self.wants_own_bytes_in_result()
12944
+ }
12945
+
12694
12946
  fn needs_image_processing(&self) -> bool {
12695
12947
  #[allow(clippy::needless_update)]
12696
12948
  let core_self = xberg::ExtractionConfig {
@@ -19264,6 +19516,7 @@ pub struct LlmConfig {
19264
19516
  bedrock: Option<BedrockConfig>,
19265
19517
  credential_provider: Option<CredentialProviderConfig>,
19266
19518
  max_concurrency: Option<usize>,
19519
+ max_response_bytes: Option<usize>,
19267
19520
  }
19268
19521
 
19269
19522
  unsafe impl IntoValueFromNative for LlmConfig {}
@@ -19561,6 +19814,15 @@ impl LlmConfig {
19561
19814
  })?),
19562
19815
  None => None,
19563
19816
  },
19817
+ max_response_bytes: match kwargs.get(ruby.to_symbol("max_response_bytes")).filter(|v| !v.is_nil()) {
19818
+ Some(v) => Some(usize::try_convert(v).map_err(|e| {
19819
+ magnus::Error::new(
19820
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
19821
+ format!("invalid value for `max_response_bytes`: {}", e),
19822
+ )
19823
+ })?),
19824
+ None => None,
19825
+ },
19564
19826
  })
19565
19827
  }
19566
19828
 
@@ -19672,6 +19934,10 @@ impl LlmConfig {
19672
19934
  self.max_concurrency
19673
19935
  }
19674
19936
 
19937
+ fn max_response_bytes(&self) -> Option<usize> {
19938
+ self.max_response_bytes
19939
+ }
19940
+
19675
19941
  fn validate(&self) -> Result<(), Error> {
19676
19942
  #[allow(clippy::needless_update)]
19677
19943
  let core_self = xberg::LlmConfig {
@@ -19732,6 +19998,8 @@ impl LlmConfig {
19732
19998
 
19733
19999
  max_concurrency: self.max_concurrency,
19734
20000
 
20001
+ max_response_bytes: self.max_response_bytes,
20002
+
19735
20003
  ..Default::default()
19736
20004
  };
19737
20005
  let result = core_self.validate().map_err(|e| {
@@ -22778,6 +23046,8 @@ pub struct OcrQualityThresholds {
22778
23046
  min_undecodable_ratio: f64,
22779
23047
  enable_provenance_ocr_routing: bool,
22780
23048
  min_provenance_fallback_ratio: f64,
23049
+ enable_plausibility_ocr_routing: bool,
23050
+ min_reliable_language_chunk_ratio: f64,
22781
23051
  }
22782
23052
 
22783
23053
  unsafe impl IntoValueFromNative for OcrQualityThresholds {}
@@ -23049,6 +23319,24 @@ impl OcrQualityThresholds {
23049
23319
  })?,
23050
23320
  None => 0.5,
23051
23321
  },
23322
+ enable_plausibility_ocr_routing: match kwargs.get(ruby.to_symbol("enable_plausibility_ocr_routing")) {
23323
+ Some(v) => bool::try_convert(v).map_err(|e| {
23324
+ magnus::Error::new(
23325
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
23326
+ format!("invalid value for `enable_plausibility_ocr_routing`: {}", e),
23327
+ )
23328
+ })?,
23329
+ None => true,
23330
+ },
23331
+ min_reliable_language_chunk_ratio: match kwargs.get(ruby.to_symbol("min_reliable_language_chunk_ratio")) {
23332
+ Some(v) => f64::try_convert(v).map_err(|e| {
23333
+ magnus::Error::new(
23334
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
23335
+ format!("invalid value for `min_reliable_language_chunk_ratio`: {}", e),
23336
+ )
23337
+ })?,
23338
+ None => 0.1,
23339
+ },
23052
23340
  })
23053
23341
  }
23054
23342
 
@@ -23147,6 +23435,14 @@ impl OcrQualityThresholds {
23147
23435
  fn min_provenance_fallback_ratio(&self) -> f64 {
23148
23436
  self.min_provenance_fallback_ratio
23149
23437
  }
23438
+
23439
+ fn enable_plausibility_ocr_routing(&self) -> bool {
23440
+ self.enable_plausibility_ocr_routing
23441
+ }
23442
+
23443
+ fn min_reliable_language_chunk_ratio(&self) -> f64 {
23444
+ self.min_reliable_language_chunk_ratio
23445
+ }
23150
23446
  }
23151
23447
 
23152
23448
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -26636,6 +26932,8 @@ pub struct PdfMetadata {
26636
26932
  page_count: Option<u32>,
26637
26933
  scanned_confidence: Option<f32>,
26638
26934
  scanned_pages: Option<Vec<u32>>,
26935
+ fabricated_text_pages: Option<Vec<u32>>,
26936
+ implausible_text_pages: Option<Vec<u32>>,
26639
26937
  layout_gated_pages: Option<Vec<u32>>,
26640
26938
  layout_gate_reasons: Option<Vec<String>>,
26641
26939
  }
@@ -26758,6 +27056,30 @@ impl PdfMetadata {
26758
27056
  })?),
26759
27057
  None => None,
26760
27058
  },
27059
+ fabricated_text_pages: match kwargs
27060
+ .get(ruby.to_symbol("fabricated_text_pages"))
27061
+ .filter(|v| !v.is_nil())
27062
+ {
27063
+ Some(v) => Some(<Vec<u32>>::try_convert(v).map_err(|e| {
27064
+ magnus::Error::new(
27065
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
27066
+ format!("invalid value for `fabricated_text_pages`: {}", e),
27067
+ )
27068
+ })?),
27069
+ None => None,
27070
+ },
27071
+ implausible_text_pages: match kwargs
27072
+ .get(ruby.to_symbol("implausible_text_pages"))
27073
+ .filter(|v| !v.is_nil())
27074
+ {
27075
+ Some(v) => Some(<Vec<u32>>::try_convert(v).map_err(|e| {
27076
+ magnus::Error::new(
27077
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
27078
+ format!("invalid value for `implausible_text_pages`: {}", e),
27079
+ )
27080
+ })?),
27081
+ None => None,
27082
+ },
26761
27083
  layout_gated_pages: match kwargs.get(ruby.to_symbol("layout_gated_pages")).filter(|v| !v.is_nil()) {
26762
27084
  Some(v) => Some(<Vec<u32>>::try_convert(v).map_err(|e| {
26763
27085
  magnus::Error::new(
@@ -26814,6 +27136,14 @@ impl PdfMetadata {
26814
27136
  self.scanned_pages.clone()
26815
27137
  }
26816
27138
 
27139
+ fn fabricated_text_pages(&self) -> Option<Vec<u32>> {
27140
+ self.fabricated_text_pages.clone()
27141
+ }
27142
+
27143
+ fn implausible_text_pages(&self) -> Option<Vec<u32>> {
27144
+ self.implausible_text_pages.clone()
27145
+ }
27146
+
26817
27147
  fn layout_gated_pages(&self) -> Option<Vec<u32>> {
26818
27148
  self.layout_gated_pages.clone()
26819
27149
  }
@@ -35293,7 +35623,8 @@ impl magnus::IntoValue for AnnotationKind {
35293
35623
 
35294
35624
  impl magnus::TryConvert for AnnotationKind {
35295
35625
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35296
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
35626
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
35627
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
35297
35628
  // For unit enums or when passed as a string, fall back to string-based conversion.
35298
35629
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
35299
35630
  s
@@ -35325,9 +35656,10 @@ impl magnus::TryConvert for AnnotationKind {
35325
35656
 
35326
35657
  unsafe impl IntoValueFromNative for AnnotationKind {}
35327
35658
  unsafe impl TryConvertOwned for AnnotationKind {}
35659
+
35328
35660
  impl AnnotationKind {}
35329
35661
 
35330
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
35662
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35331
35663
  #[serde(rename_all = "snake_case")]
35332
35664
  pub enum AssetCategory {
35333
35665
  Document,
@@ -35368,7 +35700,14 @@ impl magnus::IntoValue for AssetCategory {
35368
35700
 
35369
35701
  impl magnus::TryConvert for AssetCategory {
35370
35702
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35371
- let s: String = magnus::TryConvert::try_convert(val)?;
35703
+ // IntoValue emits Symbols, including when this enum is a Hash key.
35704
+ // Accept that exact representation as well as String inputs; do not
35705
+ // coerce arbitrary objects through to_s.
35706
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
35707
+ symbol.name()?.into_owned()
35708
+ } else {
35709
+ magnus::TryConvert::try_convert(val)?
35710
+ };
35372
35711
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35373
35712
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35374
35713
  // existing consumer code written in any of those styles keep working.
@@ -35425,7 +35764,8 @@ impl magnus::IntoValue for AuthConfig {
35425
35764
 
35426
35765
  impl magnus::TryConvert for AuthConfig {
35427
35766
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35428
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
35767
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
35768
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
35429
35769
  // For unit enums or when passed as a string, fall back to string-based conversion.
35430
35770
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
35431
35771
  s
@@ -35457,9 +35797,10 @@ impl magnus::TryConvert for AuthConfig {
35457
35797
 
35458
35798
  unsafe impl IntoValueFromNative for AuthConfig {}
35459
35799
  unsafe impl TryConvertOwned for AuthConfig {}
35800
+
35460
35801
  impl AuthConfig {}
35461
35802
 
35462
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
35803
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35463
35804
  #[serde(rename_all = "snake_case")]
35464
35805
  pub enum BlockType {
35465
35806
  Paragraph,
@@ -35512,7 +35853,14 @@ impl magnus::IntoValue for BlockType {
35512
35853
 
35513
35854
  impl magnus::TryConvert for BlockType {
35514
35855
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35515
- let s: String = magnus::TryConvert::try_convert(val)?;
35856
+ // IntoValue emits Symbols, including when this enum is a Hash key.
35857
+ // Accept that exact representation as well as String inputs; do not
35858
+ // coerce arbitrary objects through to_s.
35859
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
35860
+ symbol.name()?.into_owned()
35861
+ } else {
35862
+ magnus::TryConvert::try_convert(val)?
35863
+ };
35516
35864
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35517
35865
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35518
35866
  // existing consumer code written in any of those styles keep working.
@@ -35544,7 +35892,7 @@ impl magnus::TryConvert for BlockType {
35544
35892
  unsafe impl IntoValueFromNative for BlockType {}
35545
35893
  unsafe impl TryConvertOwned for BlockType {}
35546
35894
 
35547
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
35895
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35548
35896
  #[serde(rename_all = "snake_case")]
35549
35897
  pub enum BoundaryReason {
35550
35898
  Start,
@@ -35575,7 +35923,14 @@ impl magnus::IntoValue for BoundaryReason {
35575
35923
 
35576
35924
  impl magnus::TryConvert for BoundaryReason {
35577
35925
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35578
- let s: String = magnus::TryConvert::try_convert(val)?;
35926
+ // IntoValue emits Symbols, including when this enum is a Hash key.
35927
+ // Accept that exact representation as well as String inputs; do not
35928
+ // coerce arbitrary objects through to_s.
35929
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
35930
+ symbol.name()?.into_owned()
35931
+ } else {
35932
+ magnus::TryConvert::try_convert(val)?
35933
+ };
35579
35934
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35580
35935
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35581
35936
  // existing consumer code written in any of those styles keep working.
@@ -35596,7 +35951,7 @@ impl magnus::TryConvert for BoundaryReason {
35596
35951
  unsafe impl IntoValueFromNative for BoundaryReason {}
35597
35952
  unsafe impl TryConvertOwned for BoundaryReason {}
35598
35953
 
35599
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
35954
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35600
35955
  #[serde(rename_all = "snake_case")]
35601
35956
  pub enum BrowserBackend {
35602
35957
  Chromiumoxide,
@@ -35621,7 +35976,14 @@ impl magnus::IntoValue for BrowserBackend {
35621
35976
 
35622
35977
  impl magnus::TryConvert for BrowserBackend {
35623
35978
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35624
- let s: String = magnus::TryConvert::try_convert(val)?;
35979
+ // IntoValue emits Symbols, including when this enum is a Hash key.
35980
+ // Accept that exact representation as well as String inputs; do not
35981
+ // coerce arbitrary objects through to_s.
35982
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
35983
+ symbol.name()?.into_owned()
35984
+ } else {
35985
+ magnus::TryConvert::try_convert(val)?
35986
+ };
35625
35987
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35626
35988
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35627
35989
  // existing consumer code written in any of those styles keep working.
@@ -35639,7 +36001,7 @@ impl magnus::TryConvert for BrowserBackend {
35639
36001
  unsafe impl IntoValueFromNative for BrowserBackend {}
35640
36002
  unsafe impl TryConvertOwned for BrowserBackend {}
35641
36003
 
35642
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36004
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35643
36005
  #[serde(rename_all = "snake_case")]
35644
36006
  pub enum BrowserMode {
35645
36007
  Auto,
@@ -35668,7 +36030,14 @@ impl magnus::IntoValue for BrowserMode {
35668
36030
 
35669
36031
  impl magnus::TryConvert for BrowserMode {
35670
36032
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35671
- let s: String = magnus::TryConvert::try_convert(val)?;
36033
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36034
+ // Accept that exact representation as well as String inputs; do not
36035
+ // coerce arbitrary objects through to_s.
36036
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36037
+ symbol.name()?.into_owned()
36038
+ } else {
36039
+ magnus::TryConvert::try_convert(val)?
36040
+ };
35672
36041
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35673
36042
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35674
36043
  // existing consumer code written in any of those styles keep working.
@@ -35688,7 +36057,7 @@ impl magnus::TryConvert for BrowserMode {
35688
36057
  unsafe impl IntoValueFromNative for BrowserMode {}
35689
36058
  unsafe impl TryConvertOwned for BrowserMode {}
35690
36059
 
35691
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36060
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35692
36061
  #[serde(rename_all = "snake_case")]
35693
36062
  pub enum BrowserWait {
35694
36063
  NetworkIdle,
@@ -35715,7 +36084,14 @@ impl magnus::IntoValue for BrowserWait {
35715
36084
 
35716
36085
  impl magnus::TryConvert for BrowserWait {
35717
36086
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35718
- let s: String = magnus::TryConvert::try_convert(val)?;
36087
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36088
+ // Accept that exact representation as well as String inputs; do not
36089
+ // coerce arbitrary objects through to_s.
36090
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36091
+ symbol.name()?.into_owned()
36092
+ } else {
36093
+ magnus::TryConvert::try_convert(val)?
36094
+ };
35719
36095
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35720
36096
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35721
36097
  // existing consumer code written in any of those styles keep working.
@@ -35734,7 +36110,7 @@ impl magnus::TryConvert for BrowserWait {
35734
36110
  unsafe impl IntoValueFromNative for BrowserWait {}
35735
36111
  unsafe impl TryConvertOwned for BrowserWait {}
35736
36112
 
35737
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36113
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35738
36114
  #[serde(rename_all = "snake_case")]
35739
36115
  pub enum CallMode {
35740
36116
  TextOnly,
@@ -35761,7 +36137,14 @@ impl magnus::IntoValue for CallMode {
35761
36137
 
35762
36138
  impl magnus::TryConvert for CallMode {
35763
36139
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35764
- let s: String = magnus::TryConvert::try_convert(val)?;
36140
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36141
+ // Accept that exact representation as well as String inputs; do not
36142
+ // coerce arbitrary objects through to_s.
36143
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36144
+ symbol.name()?.into_owned()
36145
+ } else {
36146
+ magnus::TryConvert::try_convert(val)?
36147
+ };
35765
36148
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35766
36149
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35767
36150
  // existing consumer code written in any of those styles keep working.
@@ -35780,7 +36163,63 @@ impl magnus::TryConvert for CallMode {
35780
36163
  unsafe impl IntoValueFromNative for CallMode {}
35781
36164
  unsafe impl TryConvertOwned for CallMode {}
35782
36165
 
35783
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36166
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36167
+ #[serde(rename_all = "lowercase")]
36168
+ pub enum CandleDeepseekOcrDtype {
36169
+ Auto,
36170
+ F32,
36171
+ F16,
36172
+ Bf16,
36173
+ }
36174
+
36175
+ impl Default for CandleDeepseekOcrDtype {
36176
+ fn default() -> Self {
36177
+ Self::Auto
36178
+ }
36179
+ }
36180
+
36181
+ impl magnus::IntoValue for CandleDeepseekOcrDtype {
36182
+ fn into_value_with(self, handle: &Ruby) -> magnus::Value {
36183
+ let sym = match self {
36184
+ CandleDeepseekOcrDtype::Auto => "auto",
36185
+ CandleDeepseekOcrDtype::F32 => "f32",
36186
+ CandleDeepseekOcrDtype::F16 => "f16",
36187
+ CandleDeepseekOcrDtype::Bf16 => "bf16",
36188
+ };
36189
+ handle.to_symbol(sym).into_value_with(handle)
36190
+ }
36191
+ }
36192
+
36193
+ impl magnus::TryConvert for CandleDeepseekOcrDtype {
36194
+ fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36195
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36196
+ // Accept that exact representation as well as String inputs; do not
36197
+ // coerce arbitrary objects through to_s.
36198
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36199
+ symbol.name()?.into_owned()
36200
+ } else {
36201
+ magnus::TryConvert::try_convert(val)?
36202
+ };
36203
+ // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36204
+ // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36205
+ // existing consumer code written in any of those styles keep working.
36206
+ match s.as_str() {
36207
+ "auto" | "Auto" => Ok(CandleDeepseekOcrDtype::Auto),
36208
+ "f32" | "F32" => Ok(CandleDeepseekOcrDtype::F32),
36209
+ "f16" | "F16" => Ok(CandleDeepseekOcrDtype::F16),
36210
+ "bf16" | "Bf16" => Ok(CandleDeepseekOcrDtype::Bf16),
36211
+ other => Err(magnus::Error::new(
36212
+ unsafe { Ruby::get_unchecked() }.exception_arg_error(),
36213
+ format!("invalid CandleDeepseekOcrDtype value: {other}"),
36214
+ )),
36215
+ }
36216
+ }
36217
+ }
36218
+
36219
+ unsafe impl IntoValueFromNative for CandleDeepseekOcrDtype {}
36220
+ unsafe impl TryConvertOwned for CandleDeepseekOcrDtype {}
36221
+
36222
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35784
36223
  #[serde(rename_all = "lowercase")]
35785
36224
  pub enum CandleDevicePreference {
35786
36225
  Auto,
@@ -35809,7 +36248,14 @@ impl magnus::IntoValue for CandleDevicePreference {
35809
36248
 
35810
36249
  impl magnus::TryConvert for CandleDevicePreference {
35811
36250
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35812
- let s: String = magnus::TryConvert::try_convert(val)?;
36251
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36252
+ // Accept that exact representation as well as String inputs; do not
36253
+ // coerce arbitrary objects through to_s.
36254
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36255
+ symbol.name()?.into_owned()
36256
+ } else {
36257
+ magnus::TryConvert::try_convert(val)?
36258
+ };
35813
36259
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35814
36260
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35815
36261
  // existing consumer code written in any of those styles keep working.
@@ -35829,7 +36275,7 @@ impl magnus::TryConvert for CandleDevicePreference {
35829
36275
  unsafe impl IntoValueFromNative for CandleDevicePreference {}
35830
36276
  unsafe impl TryConvertOwned for CandleDevicePreference {}
35831
36277
 
35832
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36278
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35833
36279
  #[serde(rename_all = "kebab-case")]
35834
36280
  pub enum CandleTrocrVariant {
35835
36281
  BasePrinted,
@@ -35858,7 +36304,14 @@ impl magnus::IntoValue for CandleTrocrVariant {
35858
36304
 
35859
36305
  impl magnus::TryConvert for CandleTrocrVariant {
35860
36306
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35861
- let s: String = magnus::TryConvert::try_convert(val)?;
36307
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36308
+ // Accept that exact representation as well as String inputs; do not
36309
+ // coerce arbitrary objects through to_s.
36310
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36311
+ symbol.name()?.into_owned()
36312
+ } else {
36313
+ magnus::TryConvert::try_convert(val)?
36314
+ };
35862
36315
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35863
36316
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35864
36317
  // existing consumer code written in any of those styles keep working.
@@ -35903,7 +36356,8 @@ impl magnus::IntoValue for ChunkSizing {
35903
36356
 
35904
36357
  impl magnus::TryConvert for ChunkSizing {
35905
36358
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35906
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
36359
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
36360
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
35907
36361
  // For unit enums or when passed as a string, fall back to string-based conversion.
35908
36362
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
35909
36363
  s
@@ -35935,9 +36389,10 @@ impl magnus::TryConvert for ChunkSizing {
35935
36389
 
35936
36390
  unsafe impl IntoValueFromNative for ChunkSizing {}
35937
36391
  unsafe impl TryConvertOwned for ChunkSizing {}
36392
+
35938
36393
  impl ChunkSizing {}
35939
36394
 
35940
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36395
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
35941
36396
  #[serde(rename_all = "snake_case")]
35942
36397
  pub enum ChunkType {
35943
36398
  Heading,
@@ -35990,7 +36445,14 @@ impl magnus::IntoValue for ChunkType {
35990
36445
 
35991
36446
  impl magnus::TryConvert for ChunkType {
35992
36447
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
35993
- let s: String = magnus::TryConvert::try_convert(val)?;
36448
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36449
+ // Accept that exact representation as well as String inputs; do not
36450
+ // coerce arbitrary objects through to_s.
36451
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36452
+ symbol.name()?.into_owned()
36453
+ } else {
36454
+ magnus::TryConvert::try_convert(val)?
36455
+ };
35994
36456
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
35995
36457
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
35996
36458
  // existing consumer code written in any of those styles keep working.
@@ -36022,7 +36484,7 @@ impl magnus::TryConvert for ChunkType {
36022
36484
  unsafe impl IntoValueFromNative for ChunkType {}
36023
36485
  unsafe impl TryConvertOwned for ChunkType {}
36024
36486
 
36025
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36487
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36026
36488
  #[serde(rename_all = "lowercase")]
36027
36489
  pub enum ChunkerType {
36028
36490
  Text,
@@ -36051,7 +36513,14 @@ impl magnus::IntoValue for ChunkerType {
36051
36513
 
36052
36514
  impl magnus::TryConvert for ChunkerType {
36053
36515
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36054
- let s: String = magnus::TryConvert::try_convert(val)?;
36516
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36517
+ // Accept that exact representation as well as String inputs; do not
36518
+ // coerce arbitrary objects through to_s.
36519
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36520
+ symbol.name()?.into_owned()
36521
+ } else {
36522
+ magnus::TryConvert::try_convert(val)?
36523
+ };
36055
36524
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36056
36525
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36057
36526
  // existing consumer code written in any of those styles keep working.
@@ -36100,7 +36569,8 @@ impl magnus::IntoValue for ChunkingReason {
36100
36569
 
36101
36570
  impl magnus::TryConvert for ChunkingReason {
36102
36571
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36103
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
36572
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
36573
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
36104
36574
  // For unit enums or when passed as a string, fall back to string-based conversion.
36105
36575
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
36106
36576
  s
@@ -36132,9 +36602,10 @@ impl magnus::TryConvert for ChunkingReason {
36132
36602
 
36133
36603
  unsafe impl IntoValueFromNative for ChunkingReason {}
36134
36604
  unsafe impl TryConvertOwned for ChunkingReason {}
36605
+
36135
36606
  impl ChunkingReason {}
36136
36607
 
36137
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36608
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36138
36609
  pub enum CodeBlockStyle {
36139
36610
  Indented,
36140
36611
  Backticks,
@@ -36160,7 +36631,14 @@ impl magnus::IntoValue for CodeBlockStyle {
36160
36631
 
36161
36632
  impl magnus::TryConvert for CodeBlockStyle {
36162
36633
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36163
- let s: String = magnus::TryConvert::try_convert(val)?;
36634
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36635
+ // Accept that exact representation as well as String inputs; do not
36636
+ // coerce arbitrary objects through to_s.
36637
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36638
+ symbol.name()?.into_owned()
36639
+ } else {
36640
+ magnus::TryConvert::try_convert(val)?
36641
+ };
36164
36642
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36165
36643
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36166
36644
  // existing consumer code written in any of those styles keep working.
@@ -36179,7 +36657,7 @@ impl magnus::TryConvert for CodeBlockStyle {
36179
36657
  unsafe impl IntoValueFromNative for CodeBlockStyle {}
36180
36658
  unsafe impl TryConvertOwned for CodeBlockStyle {}
36181
36659
 
36182
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36660
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36183
36661
  #[serde(rename_all = "snake_case")]
36184
36662
  pub enum CodeContentMode {
36185
36663
  Chunks,
@@ -36206,7 +36684,14 @@ impl magnus::IntoValue for CodeContentMode {
36206
36684
 
36207
36685
  impl magnus::TryConvert for CodeContentMode {
36208
36686
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36209
- let s: String = magnus::TryConvert::try_convert(val)?;
36687
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36688
+ // Accept that exact representation as well as String inputs; do not
36689
+ // coerce arbitrary objects through to_s.
36690
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36691
+ symbol.name()?.into_owned()
36692
+ } else {
36693
+ magnus::TryConvert::try_convert(val)?
36694
+ };
36210
36695
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36211
36696
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36212
36697
  // existing consumer code written in any of those styles keep working.
@@ -36225,7 +36710,7 @@ impl magnus::TryConvert for CodeContentMode {
36225
36710
  unsafe impl IntoValueFromNative for CodeContentMode {}
36226
36711
  unsafe impl TryConvertOwned for CodeContentMode {}
36227
36712
 
36228
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36713
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36229
36714
  #[serde(rename_all = "snake_case")]
36230
36715
  pub enum CodeDataNodeKind {
36231
36716
  KeyValue,
@@ -36252,7 +36737,14 @@ impl magnus::IntoValue for CodeDataNodeKind {
36252
36737
 
36253
36738
  impl magnus::TryConvert for CodeDataNodeKind {
36254
36739
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36255
- let s: String = magnus::TryConvert::try_convert(val)?;
36740
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36741
+ // Accept that exact representation as well as String inputs; do not
36742
+ // coerce arbitrary objects through to_s.
36743
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36744
+ symbol.name()?.into_owned()
36745
+ } else {
36746
+ magnus::TryConvert::try_convert(val)?
36747
+ };
36256
36748
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36257
36749
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36258
36750
  // existing consumer code written in any of those styles keep working.
@@ -36333,7 +36825,7 @@ impl ConfidenceSemantics {
36333
36825
  }
36334
36826
  }
36335
36827
 
36336
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36828
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36337
36829
  #[serde(rename_all = "snake_case")]
36338
36830
  pub enum ContentFilterKind {
36339
36831
  Bm25,
@@ -36356,7 +36848,14 @@ impl magnus::IntoValue for ContentFilterKind {
36356
36848
 
36357
36849
  impl magnus::TryConvert for ContentFilterKind {
36358
36850
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36359
- let s: String = magnus::TryConvert::try_convert(val)?;
36851
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36852
+ // Accept that exact representation as well as String inputs; do not
36853
+ // coerce arbitrary objects through to_s.
36854
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36855
+ symbol.name()?.into_owned()
36856
+ } else {
36857
+ magnus::TryConvert::try_convert(val)?
36858
+ };
36360
36859
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36361
36860
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36362
36861
  // existing consumer code written in any of those styles keep working.
@@ -36373,7 +36872,7 @@ impl magnus::TryConvert for ContentFilterKind {
36373
36872
  unsafe impl IntoValueFromNative for ContentFilterKind {}
36374
36873
  unsafe impl TryConvertOwned for ContentFilterKind {}
36375
36874
 
36376
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36875
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36377
36876
  #[serde(rename_all = "snake_case")]
36378
36877
  pub enum ContentLayer {
36379
36878
  Body,
@@ -36402,7 +36901,14 @@ impl magnus::IntoValue for ContentLayer {
36402
36901
 
36403
36902
  impl magnus::TryConvert for ContentLayer {
36404
36903
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36405
- let s: String = magnus::TryConvert::try_convert(val)?;
36904
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36905
+ // Accept that exact representation as well as String inputs; do not
36906
+ // coerce arbitrary objects through to_s.
36907
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36908
+ symbol.name()?.into_owned()
36909
+ } else {
36910
+ magnus::TryConvert::try_convert(val)?
36911
+ };
36406
36912
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36407
36913
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36408
36914
  // existing consumer code written in any of those styles keep working.
@@ -36422,7 +36928,7 @@ impl magnus::TryConvert for ContentLayer {
36422
36928
  unsafe impl IntoValueFromNative for ContentLayer {}
36423
36929
  unsafe impl TryConvertOwned for ContentLayer {}
36424
36930
 
36425
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
36931
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36426
36932
  #[serde(rename_all = "snake_case")]
36427
36933
  pub enum CrawlStrategyKind {
36428
36934
  Bfs,
@@ -36451,7 +36957,14 @@ impl magnus::IntoValue for CrawlStrategyKind {
36451
36957
 
36452
36958
  impl magnus::TryConvert for CrawlStrategyKind {
36453
36959
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36454
- let s: String = magnus::TryConvert::try_convert(val)?;
36960
+ // IntoValue emits Symbols, including when this enum is a Hash key.
36961
+ // Accept that exact representation as well as String inputs; do not
36962
+ // coerce arbitrary objects through to_s.
36963
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
36964
+ symbol.name()?.into_owned()
36965
+ } else {
36966
+ magnus::TryConvert::try_convert(val)?
36967
+ };
36455
36968
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36456
36969
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36457
36970
  // existing consumer code written in any of those styles keep working.
@@ -36496,17 +37009,6 @@ pub enum CredentialProviderConfig {
36496
37009
  },
36497
37010
  }
36498
37011
 
36499
- impl Default for CredentialProviderConfig {
36500
- fn default() -> Self {
36501
- Self::AzureAd {
36502
- tenant_id: Default::default(),
36503
- client_id: Default::default(),
36504
- client_secret: Default::default(),
36505
- scope: Default::default(),
36506
- }
36507
- }
36508
- }
36509
-
36510
37012
  impl magnus::IntoValue for CredentialProviderConfig {
36511
37013
  fn into_value_with(self, handle: &Ruby) -> magnus::Value {
36512
37014
  match serde_json::to_value(&self) {
@@ -36518,7 +37020,8 @@ impl magnus::IntoValue for CredentialProviderConfig {
36518
37020
 
36519
37021
  impl magnus::TryConvert for CredentialProviderConfig {
36520
37022
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36521
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
37023
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
37024
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
36522
37025
  // For unit enums or when passed as a string, fall back to string-based conversion.
36523
37026
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
36524
37027
  s
@@ -36550,6 +37053,7 @@ impl magnus::TryConvert for CredentialProviderConfig {
36550
37053
 
36551
37054
  unsafe impl IntoValueFromNative for CredentialProviderConfig {}
36552
37055
  unsafe impl TryConvertOwned for CredentialProviderConfig {}
37056
+
36553
37057
  impl CredentialProviderConfig {}
36554
37058
 
36555
37059
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -36578,7 +37082,8 @@ impl magnus::IntoValue for DiffLine {
36578
37082
 
36579
37083
  impl magnus::TryConvert for DiffLine {
36580
37084
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36581
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
37085
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
37086
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
36582
37087
  // For unit enums or when passed as a string, fall back to string-based conversion.
36583
37088
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
36584
37089
  s
@@ -36610,6 +37115,7 @@ impl magnus::TryConvert for DiffLine {
36610
37115
 
36611
37116
  unsafe impl IntoValueFromNative for DiffLine {}
36612
37117
  unsafe impl TryConvertOwned for DiffLine {}
37118
+
36613
37119
  impl DiffLine {
36614
37120
  pub fn context(&self) -> Option<&String> {
36615
37121
  match self {
@@ -36631,7 +37137,7 @@ impl DiffLine {
36631
37137
  }
36632
37138
  }
36633
37139
 
36634
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
37140
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36635
37141
  #[serde(rename_all = "snake_case")]
36636
37142
  pub enum DocumentContentEncoding {
36637
37143
  Base64,
@@ -36654,7 +37160,14 @@ impl magnus::IntoValue for DocumentContentEncoding {
36654
37160
 
36655
37161
  impl magnus::TryConvert for DocumentContentEncoding {
36656
37162
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36657
- let s: String = magnus::TryConvert::try_convert(val)?;
37163
+ // IntoValue emits Symbols, including when this enum is a Hash key.
37164
+ // Accept that exact representation as well as String inputs; do not
37165
+ // coerce arbitrary objects through to_s.
37166
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
37167
+ symbol.name()?.into_owned()
37168
+ } else {
37169
+ magnus::TryConvert::try_convert(val)?
37170
+ };
36658
37171
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36659
37172
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36660
37173
  // existing consumer code written in any of those styles keep working.
@@ -36671,7 +37184,7 @@ impl magnus::TryConvert for DocumentContentEncoding {
36671
37184
  unsafe impl IntoValueFromNative for DocumentContentEncoding {}
36672
37185
  unsafe impl TryConvertOwned for DocumentContentEncoding {}
36673
37186
 
36674
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
37187
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36675
37188
  #[serde(rename_all = "snake_case")]
36676
37189
  pub enum ElementType {
36677
37190
  Title,
@@ -36716,7 +37229,14 @@ impl magnus::IntoValue for ElementType {
36716
37229
 
36717
37230
  impl magnus::TryConvert for ElementType {
36718
37231
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36719
- let s: String = magnus::TryConvert::try_convert(val)?;
37232
+ // IntoValue emits Symbols, including when this enum is a Hash key.
37233
+ // Accept that exact representation as well as String inputs; do not
37234
+ // coerce arbitrary objects through to_s.
37235
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
37236
+ symbol.name()?.into_owned()
37237
+ } else {
37238
+ magnus::TryConvert::try_convert(val)?
37239
+ };
36720
37240
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36721
37241
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36722
37242
  // existing consumer code written in any of those styles keep working.
@@ -36750,7 +37270,7 @@ unsafe impl TryConvertOwned for ElementType {}
36750
37270
  pub enum EmbeddingModelType {
36751
37271
  Preset { name: String },
36752
37272
  Custom { model_id: String, dimensions: usize },
36753
- Llm { llm: LlmConfig },
37273
+ Llm { llm: Box<LlmConfig> },
36754
37274
  Plugin { name: String },
36755
37275
  }
36756
37276
 
@@ -36773,7 +37293,8 @@ impl magnus::IntoValue for EmbeddingModelType {
36773
37293
 
36774
37294
  impl magnus::TryConvert for EmbeddingModelType {
36775
37295
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36776
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
37296
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
37297
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
36777
37298
  // For unit enums or when passed as a string, fall back to string-based conversion.
36778
37299
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
36779
37300
  s
@@ -36805,9 +37326,10 @@ impl magnus::TryConvert for EmbeddingModelType {
36805
37326
 
36806
37327
  unsafe impl IntoValueFromNative for EmbeddingModelType {}
36807
37328
  unsafe impl TryConvertOwned for EmbeddingModelType {}
37329
+
36808
37330
  impl EmbeddingModelType {}
36809
37331
 
36810
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
37332
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36811
37333
  #[serde(rename_all = "snake_case")]
36812
37334
  pub enum EmbeddingsEmbeddingBackend {
36813
37335
  Onnx,
@@ -36832,7 +37354,14 @@ impl magnus::IntoValue for EmbeddingsEmbeddingBackend {
36832
37354
 
36833
37355
  impl magnus::TryConvert for EmbeddingsEmbeddingBackend {
36834
37356
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36835
- let s: String = magnus::TryConvert::try_convert(val)?;
37357
+ // IntoValue emits Symbols, including when this enum is a Hash key.
37358
+ // Accept that exact representation as well as String inputs; do not
37359
+ // coerce arbitrary objects through to_s.
37360
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
37361
+ symbol.name()?.into_owned()
37362
+ } else {
37363
+ magnus::TryConvert::try_convert(val)?
37364
+ };
36836
37365
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36837
37366
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36838
37367
  // existing consumer code written in any of those styles keep working.
@@ -36915,7 +37444,7 @@ impl magnus::TryConvert for EntityCategory {
36915
37444
  unsafe impl IntoValueFromNative for EntityCategory {}
36916
37445
  unsafe impl TryConvertOwned for EntityCategory {}
36917
37446
 
36918
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
37447
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36919
37448
  #[serde(rename_all = "lowercase")]
36920
37449
  pub enum ExecutionProviderType {
36921
37450
  Auto,
@@ -36946,7 +37475,14 @@ impl magnus::IntoValue for ExecutionProviderType {
36946
37475
 
36947
37476
  impl magnus::TryConvert for ExecutionProviderType {
36948
37477
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36949
- let s: String = magnus::TryConvert::try_convert(val)?;
37478
+ // IntoValue emits Symbols, including when this enum is a Hash key.
37479
+ // Accept that exact representation as well as String inputs; do not
37480
+ // coerce arbitrary objects through to_s.
37481
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
37482
+ symbol.name()?.into_owned()
37483
+ } else {
37484
+ magnus::TryConvert::try_convert(val)?
37485
+ };
36950
37486
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36951
37487
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36952
37488
  // existing consumer code written in any of those styles keep working.
@@ -36967,7 +37503,7 @@ impl magnus::TryConvert for ExecutionProviderType {
36967
37503
  unsafe impl IntoValueFromNative for ExecutionProviderType {}
36968
37504
  unsafe impl TryConvertOwned for ExecutionProviderType {}
36969
37505
 
36970
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
37506
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
36971
37507
  #[serde(rename_all = "snake_case")]
36972
37508
  pub enum ExtractInputKind {
36973
37509
  Bytes,
@@ -36992,7 +37528,14 @@ impl magnus::IntoValue for ExtractInputKind {
36992
37528
 
36993
37529
  impl magnus::TryConvert for ExtractInputKind {
36994
37530
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
36995
- let s: String = magnus::TryConvert::try_convert(val)?;
37531
+ // IntoValue emits Symbols, including when this enum is a Hash key.
37532
+ // Accept that exact representation as well as String inputs; do not
37533
+ // coerce arbitrary objects through to_s.
37534
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
37535
+ symbol.name()?.into_owned()
37536
+ } else {
37537
+ magnus::TryConvert::try_convert(val)?
37538
+ };
36996
37539
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
36997
37540
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
36998
37541
  // existing consumer code written in any of those styles keep working.
@@ -37010,7 +37553,7 @@ impl magnus::TryConvert for ExtractInputKind {
37010
37553
  unsafe impl IntoValueFromNative for ExtractInputKind {}
37011
37554
  unsafe impl TryConvertOwned for ExtractInputKind {}
37012
37555
 
37013
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
37556
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37014
37557
  #[serde(rename_all = "snake_case")]
37015
37558
  pub enum ExtractionMethod {
37016
37559
  Native,
@@ -37037,7 +37580,14 @@ impl magnus::IntoValue for ExtractionMethod {
37037
37580
 
37038
37581
  impl magnus::TryConvert for ExtractionMethod {
37039
37582
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37040
- let s: String = magnus::TryConvert::try_convert(val)?;
37583
+ // IntoValue emits Symbols, including when this enum is a Hash key.
37584
+ // Accept that exact representation as well as String inputs; do not
37585
+ // coerce arbitrary objects through to_s.
37586
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
37587
+ symbol.name()?.into_owned()
37588
+ } else {
37589
+ magnus::TryConvert::try_convert(val)?
37590
+ };
37041
37591
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37042
37592
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37043
37593
  // existing consumer code written in any of those styles keep working.
@@ -37056,7 +37606,7 @@ impl magnus::TryConvert for ExtractionMethod {
37056
37606
  unsafe impl IntoValueFromNative for ExtractionMethod {}
37057
37607
  unsafe impl TryConvertOwned for ExtractionMethod {}
37058
37608
 
37059
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
37609
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37060
37610
  #[serde(rename_all = "snake_case")]
37061
37611
  pub enum FormFieldType {
37062
37612
  Text,
@@ -37091,7 +37641,14 @@ impl magnus::IntoValue for FormFieldType {
37091
37641
 
37092
37642
  impl magnus::TryConvert for FormFieldType {
37093
37643
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37094
- let s: String = magnus::TryConvert::try_convert(val)?;
37644
+ // IntoValue emits Symbols, including when this enum is a Hash key.
37645
+ // Accept that exact representation as well as String inputs; do not
37646
+ // coerce arbitrary objects through to_s.
37647
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
37648
+ symbol.name()?.into_owned()
37649
+ } else {
37650
+ magnus::TryConvert::try_convert(val)?
37651
+ };
37095
37652
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37096
37653
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37097
37654
  // existing consumer code written in any of those styles keep working.
@@ -37124,7 +37681,7 @@ pub enum FormatMetadata {
37124
37681
  },
37125
37682
  Docx {
37126
37683
  #[serde(flatten)]
37127
- _0: DocxMetadata,
37684
+ _0: Box<DocxMetadata>,
37128
37685
  },
37129
37686
  Excel {
37130
37687
  #[serde(flatten)]
@@ -37156,7 +37713,7 @@ pub enum FormatMetadata {
37156
37713
  },
37157
37714
  Html {
37158
37715
  #[serde(flatten)]
37159
- _0: HtmlMetadata,
37716
+ _0: Box<HtmlMetadata>,
37160
37717
  },
37161
37718
  Ocr {
37162
37719
  #[serde(flatten)]
@@ -37221,7 +37778,92 @@ impl magnus::IntoValue for FormatMetadata {
37221
37778
 
37222
37779
  impl magnus::TryConvert for FormatMetadata {
37223
37780
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37224
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
37781
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
37782
+ let class_name: Option<String> = val.class().funcall("name", ())?;
37783
+ if class_name.as_deref() == Some("Xberg::FormatMetadataPdf") {
37784
+ let payload: PdfMetadata = val.funcall("value", ())?;
37785
+ return Ok(Self::Pdf { _0: payload });
37786
+ }
37787
+ if class_name.as_deref() == Some("Xberg::FormatMetadataDocx") {
37788
+ let payload: DocxMetadata = val.funcall("value", ())?;
37789
+ return Ok(Self::Docx { _0: Box::new(payload) });
37790
+ }
37791
+ if class_name.as_deref() == Some("Xberg::FormatMetadataExcel") {
37792
+ let payload: ExcelMetadata = val.funcall("value", ())?;
37793
+ return Ok(Self::Excel { _0: payload });
37794
+ }
37795
+ if class_name.as_deref() == Some("Xberg::FormatMetadataEmail") {
37796
+ let payload: EmailMetadata = val.funcall("value", ())?;
37797
+ return Ok(Self::Email { _0: payload });
37798
+ }
37799
+ if class_name.as_deref() == Some("Xberg::FormatMetadataPptx") {
37800
+ let payload: PptxMetadata = val.funcall("value", ())?;
37801
+ return Ok(Self::Pptx { _0: payload });
37802
+ }
37803
+ if class_name.as_deref() == Some("Xberg::FormatMetadataArchive") {
37804
+ let payload: ArchiveMetadata = val.funcall("value", ())?;
37805
+ return Ok(Self::Archive { _0: payload });
37806
+ }
37807
+ if class_name.as_deref() == Some("Xberg::FormatMetadataImage") {
37808
+ let payload: ImageMetadata = val.funcall("value", ())?;
37809
+ return Ok(Self::Image { _0: payload });
37810
+ }
37811
+ if class_name.as_deref() == Some("Xberg::FormatMetadataXml") {
37812
+ let payload: XmlMetadata = val.funcall("value", ())?;
37813
+ return Ok(Self::Xml { _0: payload });
37814
+ }
37815
+ if class_name.as_deref() == Some("Xberg::FormatMetadataText") {
37816
+ let payload: TextMetadata = val.funcall("value", ())?;
37817
+ return Ok(Self::Text { _0: payload });
37818
+ }
37819
+ if class_name.as_deref() == Some("Xberg::FormatMetadataHtml") {
37820
+ let payload: HtmlMetadata = val.funcall("value", ())?;
37821
+ return Ok(Self::Html { _0: Box::new(payload) });
37822
+ }
37823
+ if class_name.as_deref() == Some("Xberg::FormatMetadataOcr") {
37824
+ let payload: OcrMetadata = val.funcall("value", ())?;
37825
+ return Ok(Self::Ocr { _0: payload });
37826
+ }
37827
+ if class_name.as_deref() == Some("Xberg::FormatMetadataCsv") {
37828
+ let payload: CsvMetadata = val.funcall("value", ())?;
37829
+ return Ok(Self::Csv { _0: payload });
37830
+ }
37831
+ if class_name.as_deref() == Some("Xberg::FormatMetadataBibtex") {
37832
+ let payload: BibtexMetadata = val.funcall("value", ())?;
37833
+ return Ok(Self::Bibtex { _0: payload });
37834
+ }
37835
+ if class_name.as_deref() == Some("Xberg::FormatMetadataCitation") {
37836
+ let payload: CitationMetadata = val.funcall("value", ())?;
37837
+ return Ok(Self::Citation { _0: payload });
37838
+ }
37839
+ if class_name.as_deref() == Some("Xberg::FormatMetadataFictionBook") {
37840
+ let payload: FictionBookMetadata = val.funcall("value", ())?;
37841
+ return Ok(Self::FictionBook { _0: payload });
37842
+ }
37843
+ if class_name.as_deref() == Some("Xberg::FormatMetadataDbf") {
37844
+ let payload: DbfMetadata = val.funcall("value", ())?;
37845
+ return Ok(Self::Dbf { _0: payload });
37846
+ }
37847
+ if class_name.as_deref() == Some("Xberg::FormatMetadataJats") {
37848
+ let payload: JatsMetadata = val.funcall("value", ())?;
37849
+ return Ok(Self::Jats { _0: payload });
37850
+ }
37851
+ if class_name.as_deref() == Some("Xberg::FormatMetadataEpub") {
37852
+ let payload: EpubMetadata = val.funcall("value", ())?;
37853
+ return Ok(Self::Epub { _0: payload });
37854
+ }
37855
+ if class_name.as_deref() == Some("Xberg::FormatMetadataPst") {
37856
+ let payload: PstMetadata = val.funcall("value", ())?;
37857
+ return Ok(Self::Pst { _0: payload });
37858
+ }
37859
+ if class_name.as_deref() == Some("Xberg::FormatMetadataAudio") {
37860
+ let payload: AudioMetadata = val.funcall("value", ())?;
37861
+ return Ok(Self::Audio { _0: payload });
37862
+ }
37863
+ if class_name.as_deref() == Some("Xberg::FormatMetadataCode") {
37864
+ let payload: CodeMetadata = val.funcall("value", ())?;
37865
+ return Ok(Self::Code { _0: payload });
37866
+ } // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
37225
37867
  // For unit enums or when passed as a string, fall back to string-based conversion.
37226
37868
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
37227
37869
  s
@@ -37253,6 +37895,7 @@ impl magnus::TryConvert for FormatMetadata {
37253
37895
 
37254
37896
  unsafe impl IntoValueFromNative for FormatMetadata {}
37255
37897
  unsafe impl TryConvertOwned for FormatMetadata {}
37898
+
37256
37899
  impl FormatMetadata {
37257
37900
  pub fn pdf(&self) -> Option<&PdfMetadata> {
37258
37901
  match self {
@@ -37262,7 +37905,7 @@ impl FormatMetadata {
37262
37905
  }
37263
37906
  pub fn docx(&self) -> Option<&DocxMetadata> {
37264
37907
  match self {
37265
- Self::Docx { _0 } => Some(_0),
37908
+ Self::Docx { _0 } => Some(&**_0),
37266
37909
  _ => None,
37267
37910
  }
37268
37911
  }
@@ -37310,7 +37953,7 @@ impl FormatMetadata {
37310
37953
  }
37311
37954
  pub fn html(&self) -> Option<&HtmlMetadata> {
37312
37955
  match self {
37313
- Self::Html { _0 } => Some(_0),
37956
+ Self::Html { _0 } => Some(&**_0),
37314
37957
  _ => None,
37315
37958
  }
37316
37959
  }
@@ -37382,7 +38025,7 @@ impl FormatMetadata {
37382
38025
  }
37383
38026
  }
37384
38027
 
37385
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38028
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37386
38029
  #[serde(rename_all = "snake_case")]
37387
38030
  pub enum FormulaModel {
37388
38031
  LatexOcr,
@@ -37405,7 +38048,14 @@ impl magnus::IntoValue for FormulaModel {
37405
38048
 
37406
38049
  impl magnus::TryConvert for FormulaModel {
37407
38050
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37408
- let s: String = magnus::TryConvert::try_convert(val)?;
38051
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38052
+ // Accept that exact representation as well as String inputs; do not
38053
+ // coerce arbitrary objects through to_s.
38054
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38055
+ symbol.name()?.into_owned()
38056
+ } else {
38057
+ magnus::TryConvert::try_convert(val)?
38058
+ };
37409
38059
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37410
38060
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37411
38061
  // existing consumer code written in any of those styles keep working.
@@ -37422,7 +38072,7 @@ impl magnus::TryConvert for FormulaModel {
37422
38072
  unsafe impl IntoValueFromNative for FormulaModel {}
37423
38073
  unsafe impl TryConvertOwned for FormulaModel {}
37424
38074
 
37425
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38075
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37426
38076
  #[serde(rename_all = "snake_case")]
37427
38077
  pub enum GlmOcrLayoutMode {
37428
38078
  WholePage,
@@ -37447,7 +38097,14 @@ impl magnus::IntoValue for GlmOcrLayoutMode {
37447
38097
 
37448
38098
  impl magnus::TryConvert for GlmOcrLayoutMode {
37449
38099
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37450
- let s: String = magnus::TryConvert::try_convert(val)?;
38100
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38101
+ // Accept that exact representation as well as String inputs; do not
38102
+ // coerce arbitrary objects through to_s.
38103
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38104
+ symbol.name()?.into_owned()
38105
+ } else {
38106
+ magnus::TryConvert::try_convert(val)?
38107
+ };
37451
38108
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37452
38109
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37453
38110
  // existing consumer code written in any of those styles keep working.
@@ -37465,7 +38122,7 @@ impl magnus::TryConvert for GlmOcrLayoutMode {
37465
38122
  unsafe impl IntoValueFromNative for GlmOcrLayoutMode {}
37466
38123
  unsafe impl TryConvertOwned for GlmOcrLayoutMode {}
37467
38124
 
37468
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38125
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37469
38126
  #[serde(rename_all = "lowercase")]
37470
38127
  pub enum GlmOcrTaskKind {
37471
38128
  Ocr,
@@ -37496,7 +38153,14 @@ impl magnus::IntoValue for GlmOcrTaskKind {
37496
38153
 
37497
38154
  impl magnus::TryConvert for GlmOcrTaskKind {
37498
38155
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37499
- let s: String = magnus::TryConvert::try_convert(val)?;
38156
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38157
+ // Accept that exact representation as well as String inputs; do not
38158
+ // coerce arbitrary objects through to_s.
38159
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38160
+ symbol.name()?.into_owned()
38161
+ } else {
38162
+ magnus::TryConvert::try_convert(val)?
38163
+ };
37500
38164
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37501
38165
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37502
38166
  // existing consumer code written in any of those styles keep working.
@@ -37517,7 +38181,7 @@ impl magnus::TryConvert for GlmOcrTaskKind {
37517
38181
  unsafe impl IntoValueFromNative for GlmOcrTaskKind {}
37518
38182
  unsafe impl TryConvertOwned for GlmOcrTaskKind {}
37519
38183
 
37520
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38184
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37521
38185
  pub enum HeadingStyle {
37522
38186
  Underlined,
37523
38187
  Atx,
@@ -37543,7 +38207,14 @@ impl magnus::IntoValue for HeadingStyle {
37543
38207
 
37544
38208
  impl magnus::TryConvert for HeadingStyle {
37545
38209
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37546
- let s: String = magnus::TryConvert::try_convert(val)?;
38210
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38211
+ // Accept that exact representation as well as String inputs; do not
38212
+ // coerce arbitrary objects through to_s.
38213
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38214
+ symbol.name()?.into_owned()
38215
+ } else {
38216
+ magnus::TryConvert::try_convert(val)?
38217
+ };
37547
38218
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37548
38219
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37549
38220
  // existing consumer code written in any of those styles keep working.
@@ -37562,7 +38233,7 @@ impl magnus::TryConvert for HeadingStyle {
37562
38233
  unsafe impl IntoValueFromNative for HeadingStyle {}
37563
38234
  unsafe impl TryConvertOwned for HeadingStyle {}
37564
38235
 
37565
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38236
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37566
38237
  pub enum HighlightStyle {
37567
38238
  DoubleEqual,
37568
38239
  Html,
@@ -37590,7 +38261,14 @@ impl magnus::IntoValue for HighlightStyle {
37590
38261
 
37591
38262
  impl magnus::TryConvert for HighlightStyle {
37592
38263
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37593
- let s: String = magnus::TryConvert::try_convert(val)?;
38264
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38265
+ // Accept that exact representation as well as String inputs; do not
38266
+ // coerce arbitrary objects through to_s.
38267
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38268
+ symbol.name()?.into_owned()
38269
+ } else {
38270
+ magnus::TryConvert::try_convert(val)?
38271
+ };
37594
38272
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37595
38273
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37596
38274
  // existing consumer code written in any of those styles keep working.
@@ -37619,14 +38297,6 @@ pub enum HostMatcher {
37619
38297
  Cidr { value: String },
37620
38298
  }
37621
38299
 
37622
- impl Default for HostMatcher {
37623
- fn default() -> Self {
37624
- Self::Exact {
37625
- value: Default::default(),
37626
- }
37627
- }
37628
- }
37629
-
37630
38300
  impl magnus::IntoValue for HostMatcher {
37631
38301
  fn into_value_with(self, handle: &Ruby) -> magnus::Value {
37632
38302
  match serde_json::to_value(&self) {
@@ -37638,7 +38308,8 @@ impl magnus::IntoValue for HostMatcher {
37638
38308
 
37639
38309
  impl magnus::TryConvert for HostMatcher {
37640
38310
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37641
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
38311
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
38312
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
37642
38313
  // For unit enums or when passed as a string, fall back to string-based conversion.
37643
38314
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
37644
38315
  s
@@ -37670,9 +38341,10 @@ impl magnus::TryConvert for HostMatcher {
37670
38341
 
37671
38342
  unsafe impl IntoValueFromNative for HostMatcher {}
37672
38343
  unsafe impl TryConvertOwned for HostMatcher {}
38344
+
37673
38345
  impl HostMatcher {}
37674
38346
 
37675
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38347
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37676
38348
  #[serde(rename_all = "lowercase")]
37677
38349
  pub enum HtmlTheme {
37678
38350
  Default,
@@ -37703,7 +38375,14 @@ impl magnus::IntoValue for HtmlTheme {
37703
38375
 
37704
38376
  impl magnus::TryConvert for HtmlTheme {
37705
38377
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37706
- let s: String = magnus::TryConvert::try_convert(val)?;
38378
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38379
+ // Accept that exact representation as well as String inputs; do not
38380
+ // coerce arbitrary objects through to_s.
38381
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38382
+ symbol.name()?.into_owned()
38383
+ } else {
38384
+ magnus::TryConvert::try_convert(val)?
38385
+ };
37707
38386
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37708
38387
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37709
38388
  // existing consumer code written in any of those styles keep working.
@@ -37724,7 +38403,7 @@ impl magnus::TryConvert for HtmlTheme {
37724
38403
  unsafe impl IntoValueFromNative for HtmlTheme {}
37725
38404
  unsafe impl TryConvertOwned for HtmlTheme {}
37726
38405
 
37727
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38406
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37728
38407
  pub enum HtmlToMarkdownRsOutputFormat {
37729
38408
  Markdown,
37730
38409
  Djot,
@@ -37750,7 +38429,14 @@ impl magnus::IntoValue for HtmlToMarkdownRsOutputFormat {
37750
38429
 
37751
38430
  impl magnus::TryConvert for HtmlToMarkdownRsOutputFormat {
37752
38431
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37753
- let s: String = magnus::TryConvert::try_convert(val)?;
38432
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38433
+ // Accept that exact representation as well as String inputs; do not
38434
+ // coerce arbitrary objects through to_s.
38435
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38436
+ symbol.name()?.into_owned()
38437
+ } else {
38438
+ magnus::TryConvert::try_convert(val)?
38439
+ };
37754
38440
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37755
38441
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37756
38442
  // existing consumer code written in any of those styles keep working.
@@ -37769,7 +38455,7 @@ impl magnus::TryConvert for HtmlToMarkdownRsOutputFormat {
37769
38455
  unsafe impl IntoValueFromNative for HtmlToMarkdownRsOutputFormat {}
37770
38456
  unsafe impl TryConvertOwned for HtmlToMarkdownRsOutputFormat {}
37771
38457
 
37772
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38458
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37773
38459
  #[serde(rename_all = "snake_case")]
37774
38460
  pub enum ImageKind {
37775
38461
  Photograph,
@@ -37814,7 +38500,14 @@ impl magnus::IntoValue for ImageKind {
37814
38500
 
37815
38501
  impl magnus::TryConvert for ImageKind {
37816
38502
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37817
- let s: String = magnus::TryConvert::try_convert(val)?;
38503
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38504
+ // Accept that exact representation as well as String inputs; do not
38505
+ // coerce arbitrary objects through to_s.
38506
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38507
+ symbol.name()?.into_owned()
38508
+ } else {
38509
+ magnus::TryConvert::try_convert(val)?
38510
+ };
37818
38511
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37819
38512
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37820
38513
  // existing consumer code written in any of those styles keep working.
@@ -37871,7 +38564,8 @@ impl magnus::IntoValue for ImageOutputFormat {
37871
38564
 
37872
38565
  impl magnus::TryConvert for ImageOutputFormat {
37873
38566
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37874
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
38567
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
38568
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
37875
38569
  // For unit enums or when passed as a string, fall back to string-based conversion.
37876
38570
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
37877
38571
  s
@@ -37903,9 +38597,10 @@ impl magnus::TryConvert for ImageOutputFormat {
37903
38597
 
37904
38598
  unsafe impl IntoValueFromNative for ImageOutputFormat {}
37905
38599
  unsafe impl TryConvertOwned for ImageOutputFormat {}
38600
+
37906
38601
  impl ImageOutputFormat {}
37907
38602
 
37908
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38603
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37909
38604
  #[serde(rename_all = "lowercase")]
37910
38605
  pub enum ImageType {
37911
38606
  #[serde(rename = "data-uri")]
@@ -37936,7 +38631,14 @@ impl magnus::IntoValue for ImageType {
37936
38631
 
37937
38632
  impl magnus::TryConvert for ImageType {
37938
38633
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
37939
- let s: String = magnus::TryConvert::try_convert(val)?;
38634
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38635
+ // Accept that exact representation as well as String inputs; do not
38636
+ // coerce arbitrary objects through to_s.
38637
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38638
+ symbol.name()?.into_owned()
38639
+ } else {
38640
+ magnus::TryConvert::try_convert(val)?
38641
+ };
37940
38642
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
37941
38643
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
37942
38644
  // existing consumer code written in any of those styles keep working.
@@ -37956,7 +38658,7 @@ impl magnus::TryConvert for ImageType {
37956
38658
  unsafe impl IntoValueFromNative for ImageType {}
37957
38659
  unsafe impl TryConvertOwned for ImageType {}
37958
38660
 
37959
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38661
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
37960
38662
  #[serde(rename_all = "snake_case")]
37961
38663
  pub enum InlineType {
37962
38664
  Text,
@@ -38009,7 +38711,14 @@ impl magnus::IntoValue for InlineType {
38009
38711
 
38010
38712
  impl magnus::TryConvert for InlineType {
38011
38713
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38012
- let s: String = magnus::TryConvert::try_convert(val)?;
38714
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38715
+ // Accept that exact representation as well as String inputs; do not
38716
+ // coerce arbitrary objects through to_s.
38717
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38718
+ symbol.name()?.into_owned()
38719
+ } else {
38720
+ magnus::TryConvert::try_convert(val)?
38721
+ };
38013
38722
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38014
38723
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38015
38724
  // existing consumer code written in any of those styles keep working.
@@ -38041,7 +38750,7 @@ impl magnus::TryConvert for InlineType {
38041
38750
  unsafe impl IntoValueFromNative for InlineType {}
38042
38751
  unsafe impl TryConvertOwned for InlineType {}
38043
38752
 
38044
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38753
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38045
38754
  #[serde(rename_all = "lowercase")]
38046
38755
  pub enum JupyterCellRendering {
38047
38756
  Source,
@@ -38068,7 +38777,14 @@ impl magnus::IntoValue for JupyterCellRendering {
38068
38777
 
38069
38778
  impl magnus::TryConvert for JupyterCellRendering {
38070
38779
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38071
- let s: String = magnus::TryConvert::try_convert(val)?;
38780
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38781
+ // Accept that exact representation as well as String inputs; do not
38782
+ // coerce arbitrary objects through to_s.
38783
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38784
+ symbol.name()?.into_owned()
38785
+ } else {
38786
+ magnus::TryConvert::try_convert(val)?
38787
+ };
38072
38788
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38073
38789
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38074
38790
  // existing consumer code written in any of those styles keep working.
@@ -38087,7 +38803,7 @@ impl magnus::TryConvert for JupyterCellRendering {
38087
38803
  unsafe impl IntoValueFromNative for JupyterCellRendering {}
38088
38804
  unsafe impl TryConvertOwned for JupyterCellRendering {}
38089
38805
 
38090
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38806
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38091
38807
  #[serde(rename_all = "lowercase")]
38092
38808
  pub enum KeywordAlgorithm {
38093
38809
  Yake,
@@ -38112,7 +38828,14 @@ impl magnus::IntoValue for KeywordAlgorithm {
38112
38828
 
38113
38829
  impl magnus::TryConvert for KeywordAlgorithm {
38114
38830
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38115
- let s: String = magnus::TryConvert::try_convert(val)?;
38831
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38832
+ // Accept that exact representation as well as String inputs; do not
38833
+ // coerce arbitrary objects through to_s.
38834
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38835
+ symbol.name()?.into_owned()
38836
+ } else {
38837
+ magnus::TryConvert::try_convert(val)?
38838
+ };
38116
38839
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38117
38840
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38118
38841
  // existing consumer code written in any of those styles keep working.
@@ -38167,7 +38890,8 @@ impl magnus::IntoValue for LateInteractionModelType {
38167
38890
 
38168
38891
  impl magnus::TryConvert for LateInteractionModelType {
38169
38892
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38170
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
38893
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
38894
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
38171
38895
  // For unit enums or when passed as a string, fall back to string-based conversion.
38172
38896
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
38173
38897
  s
@@ -38199,9 +38923,10 @@ impl magnus::TryConvert for LateInteractionModelType {
38199
38923
 
38200
38924
  unsafe impl IntoValueFromNative for LateInteractionModelType {}
38201
38925
  unsafe impl TryConvertOwned for LateInteractionModelType {}
38926
+
38202
38927
  impl LateInteractionModelType {}
38203
38928
 
38204
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
38929
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38205
38930
  #[serde(rename_all = "snake_case")]
38206
38931
  pub enum LayoutClass {
38207
38932
  Caption,
@@ -38258,7 +38983,14 @@ impl magnus::IntoValue for LayoutClass {
38258
38983
 
38259
38984
  impl magnus::TryConvert for LayoutClass {
38260
38985
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38261
- let s: String = magnus::TryConvert::try_convert(val)?;
38986
+ // IntoValue emits Symbols, including when this enum is a Hash key.
38987
+ // Accept that exact representation as well as String inputs; do not
38988
+ // coerce arbitrary objects through to_s.
38989
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
38990
+ symbol.name()?.into_owned()
38991
+ } else {
38992
+ magnus::TryConvert::try_convert(val)?
38993
+ };
38262
38994
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38263
38995
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38264
38996
  // existing consumer code written in any of those styles keep working.
@@ -38292,7 +39024,7 @@ impl magnus::TryConvert for LayoutClass {
38292
39024
  unsafe impl IntoValueFromNative for LayoutClass {}
38293
39025
  unsafe impl TryConvertOwned for LayoutClass {}
38294
39026
 
38295
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39027
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38296
39028
  #[serde(rename_all = "snake_case")]
38297
39029
  pub enum LayoutStrategy {
38298
39030
  Always,
@@ -38317,7 +39049,14 @@ impl magnus::IntoValue for LayoutStrategy {
38317
39049
 
38318
39050
  impl magnus::TryConvert for LayoutStrategy {
38319
39051
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38320
- let s: String = magnus::TryConvert::try_convert(val)?;
39052
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39053
+ // Accept that exact representation as well as String inputs; do not
39054
+ // coerce arbitrary objects through to_s.
39055
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39056
+ symbol.name()?.into_owned()
39057
+ } else {
39058
+ magnus::TryConvert::try_convert(val)?
39059
+ };
38321
39060
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38322
39061
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38323
39062
  // existing consumer code written in any of those styles keep working.
@@ -38335,7 +39074,7 @@ impl magnus::TryConvert for LayoutStrategy {
38335
39074
  unsafe impl IntoValueFromNative for LayoutStrategy {}
38336
39075
  unsafe impl TryConvertOwned for LayoutStrategy {}
38337
39076
 
38338
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39077
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38339
39078
  pub enum LinkStyle {
38340
39079
  Inline,
38341
39080
  Reference,
@@ -38359,7 +39098,14 @@ impl magnus::IntoValue for LinkStyle {
38359
39098
 
38360
39099
  impl magnus::TryConvert for LinkStyle {
38361
39100
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38362
- let s: String = magnus::TryConvert::try_convert(val)?;
39101
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39102
+ // Accept that exact representation as well as String inputs; do not
39103
+ // coerce arbitrary objects through to_s.
39104
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39105
+ symbol.name()?.into_owned()
39106
+ } else {
39107
+ magnus::TryConvert::try_convert(val)?
39108
+ };
38363
39109
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38364
39110
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38365
39111
  // existing consumer code written in any of those styles keep working.
@@ -38377,7 +39123,7 @@ impl magnus::TryConvert for LinkStyle {
38377
39123
  unsafe impl IntoValueFromNative for LinkStyle {}
38378
39124
  unsafe impl TryConvertOwned for LinkStyle {}
38379
39125
 
38380
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39126
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38381
39127
  #[serde(rename_all = "lowercase")]
38382
39128
  pub enum LinkType {
38383
39129
  Anchor,
@@ -38410,7 +39156,14 @@ impl magnus::IntoValue for LinkType {
38410
39156
 
38411
39157
  impl magnus::TryConvert for LinkType {
38412
39158
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38413
- let s: String = magnus::TryConvert::try_convert(val)?;
39159
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39160
+ // Accept that exact representation as well as String inputs; do not
39161
+ // coerce arbitrary objects through to_s.
39162
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39163
+ symbol.name()?.into_owned()
39164
+ } else {
39165
+ magnus::TryConvert::try_convert(val)?
39166
+ };
38414
39167
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38415
39168
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38416
39169
  // existing consumer code written in any of those styles keep working.
@@ -38432,7 +39185,7 @@ impl magnus::TryConvert for LinkType {
38432
39185
  unsafe impl IntoValueFromNative for LinkType {}
38433
39186
  unsafe impl TryConvertOwned for LinkType {}
38434
39187
 
38435
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39188
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38436
39189
  pub enum ListIndentType {
38437
39190
  Spaces,
38438
39191
  Tabs,
@@ -38456,7 +39209,14 @@ impl magnus::IntoValue for ListIndentType {
38456
39209
 
38457
39210
  impl magnus::TryConvert for ListIndentType {
38458
39211
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38459
- let s: String = magnus::TryConvert::try_convert(val)?;
39212
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39213
+ // Accept that exact representation as well as String inputs; do not
39214
+ // coerce arbitrary objects through to_s.
39215
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39216
+ symbol.name()?.into_owned()
39217
+ } else {
39218
+ magnus::TryConvert::try_convert(val)?
39219
+ };
38460
39220
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38461
39221
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38462
39222
  // existing consumer code written in any of those styles keep working.
@@ -38474,7 +39234,7 @@ impl magnus::TryConvert for ListIndentType {
38474
39234
  unsafe impl IntoValueFromNative for ListIndentType {}
38475
39235
  unsafe impl TryConvertOwned for ListIndentType {}
38476
39236
 
38477
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39237
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38478
39238
  pub enum ListType {
38479
39239
  Bullet,
38480
39240
  Numbered,
@@ -38502,7 +39262,14 @@ impl magnus::IntoValue for ListType {
38502
39262
 
38503
39263
  impl magnus::TryConvert for ListType {
38504
39264
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38505
- let s: String = magnus::TryConvert::try_convert(val)?;
39265
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39266
+ // Accept that exact representation as well as String inputs; do not
39267
+ // coerce arbitrary objects through to_s.
39268
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39269
+ symbol.name()?.into_owned()
39270
+ } else {
39271
+ magnus::TryConvert::try_convert(val)?
39272
+ };
38506
39273
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38507
39274
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38508
39275
  // existing consumer code written in any of those styles keep working.
@@ -38522,7 +39289,7 @@ impl magnus::TryConvert for ListType {
38522
39289
  unsafe impl IntoValueFromNative for ListType {}
38523
39290
  unsafe impl TryConvertOwned for ListType {}
38524
39291
 
38525
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39292
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38526
39293
  #[serde(rename_all = "snake_case")]
38527
39294
  pub enum MergeMode {
38528
39295
  ObjectMerge,
@@ -38549,7 +39316,14 @@ impl magnus::IntoValue for MergeMode {
38549
39316
 
38550
39317
  impl magnus::TryConvert for MergeMode {
38551
39318
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38552
- let s: String = magnus::TryConvert::try_convert(val)?;
39319
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39320
+ // Accept that exact representation as well as String inputs; do not
39321
+ // coerce arbitrary objects through to_s.
39322
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39323
+ symbol.name()?.into_owned()
39324
+ } else {
39325
+ magnus::TryConvert::try_convert(val)?
39326
+ };
38553
39327
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38554
39328
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38555
39329
  // existing consumer code written in any of those styles keep working.
@@ -38568,7 +39342,7 @@ impl magnus::TryConvert for MergeMode {
38568
39342
  unsafe impl IntoValueFromNative for MergeMode {}
38569
39343
  unsafe impl TryConvertOwned for MergeMode {}
38570
39344
 
38571
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39345
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38572
39346
  #[serde(rename_all = "snake_case")]
38573
39347
  pub enum MimeDetectionPolicy {
38574
39348
  PreferContent,
@@ -38595,7 +39369,14 @@ impl magnus::IntoValue for MimeDetectionPolicy {
38595
39369
 
38596
39370
  impl magnus::TryConvert for MimeDetectionPolicy {
38597
39371
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38598
- let s: String = magnus::TryConvert::try_convert(val)?;
39372
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39373
+ // Accept that exact representation as well as String inputs; do not
39374
+ // coerce arbitrary objects through to_s.
39375
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39376
+ symbol.name()?.into_owned()
39377
+ } else {
39378
+ magnus::TryConvert::try_convert(val)?
39379
+ };
38599
39380
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38600
39381
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38601
39382
  // existing consumer code written in any of those styles keep working.
@@ -38614,7 +39395,7 @@ impl magnus::TryConvert for MimeDetectionPolicy {
38614
39395
  unsafe impl IntoValueFromNative for MimeDetectionPolicy {}
38615
39396
  unsafe impl TryConvertOwned for MimeDetectionPolicy {}
38616
39397
 
38617
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39398
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38618
39399
  #[serde(rename_all = "snake_case")]
38619
39400
  pub enum NerBackendKind {
38620
39401
  Onnx,
@@ -38639,7 +39420,14 @@ impl magnus::IntoValue for NerBackendKind {
38639
39420
 
38640
39421
  impl magnus::TryConvert for NerBackendKind {
38641
39422
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38642
- let s: String = magnus::TryConvert::try_convert(val)?;
39423
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39424
+ // Accept that exact representation as well as String inputs; do not
39425
+ // coerce arbitrary objects through to_s.
39426
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39427
+ symbol.name()?.into_owned()
39428
+ } else {
39429
+ magnus::TryConvert::try_convert(val)?
39430
+ };
38643
39431
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38644
39432
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38645
39433
  // existing consumer code written in any of those styles keep working.
@@ -38657,7 +39445,7 @@ impl magnus::TryConvert for NerBackendKind {
38657
39445
  unsafe impl IntoValueFromNative for NerBackendKind {}
38658
39446
  unsafe impl TryConvertOwned for NerBackendKind {}
38659
39447
 
38660
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39448
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38661
39449
  pub enum NewlineStyle {
38662
39450
  Spaces,
38663
39451
  Backslash,
@@ -38681,7 +39469,14 @@ impl magnus::IntoValue for NewlineStyle {
38681
39469
 
38682
39470
  impl magnus::TryConvert for NewlineStyle {
38683
39471
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38684
- let s: String = magnus::TryConvert::try_convert(val)?;
39472
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39473
+ // Accept that exact representation as well as String inputs; do not
39474
+ // coerce arbitrary objects through to_s.
39475
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39476
+ symbol.name()?.into_owned()
39477
+ } else {
39478
+ magnus::TryConvert::try_convert(val)?
39479
+ };
38685
39480
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38686
39481
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38687
39482
  // existing consumer code written in any of those styles keep working.
@@ -38738,7 +39533,8 @@ impl magnus::IntoValue for NoChunkingReason {
38738
39533
 
38739
39534
  impl magnus::TryConvert for NoChunkingReason {
38740
39535
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38741
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
39536
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
39537
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
38742
39538
  // For unit enums or when passed as a string, fall back to string-based conversion.
38743
39539
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
38744
39540
  s
@@ -38770,6 +39566,7 @@ impl magnus::TryConvert for NoChunkingReason {
38770
39566
 
38771
39567
  unsafe impl IntoValueFromNative for NoChunkingReason {}
38772
39568
  unsafe impl TryConvertOwned for NoChunkingReason {}
39569
+
38773
39570
  impl NoChunkingReason {}
38774
39571
 
38775
39572
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -38865,7 +39662,8 @@ impl magnus::IntoValue for NodeContent {
38865
39662
 
38866
39663
  impl magnus::TryConvert for NodeContent {
38867
39664
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38868
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
39665
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
39666
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
38869
39667
  // For unit enums or when passed as a string, fall back to string-based conversion.
38870
39668
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
38871
39669
  s
@@ -38897,9 +39695,10 @@ impl magnus::TryConvert for NodeContent {
38897
39695
 
38898
39696
  unsafe impl IntoValueFromNative for NodeContent {}
38899
39697
  unsafe impl TryConvertOwned for NodeContent {}
39698
+
38900
39699
  impl NodeContent {}
38901
39700
 
38902
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39701
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
38903
39702
  pub enum OcrBackendType {
38904
39703
  Tesseract,
38905
39704
  PaddleOCR,
@@ -38927,7 +39726,14 @@ impl magnus::IntoValue for OcrBackendType {
38927
39726
 
38928
39727
  impl magnus::TryConvert for OcrBackendType {
38929
39728
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38930
- let s: String = magnus::TryConvert::try_convert(val)?;
39729
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39730
+ // Accept that exact representation as well as String inputs; do not
39731
+ // coerce arbitrary objects through to_s.
39732
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39733
+ symbol.name()?.into_owned()
39734
+ } else {
39735
+ magnus::TryConvert::try_convert(val)?
39736
+ };
38931
39737
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
38932
39738
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
38933
39739
  // existing consumer code written in any of those styles keep working.
@@ -38984,7 +39790,8 @@ impl magnus::IntoValue for OcrBoundingGeometry {
38984
39790
 
38985
39791
  impl magnus::TryConvert for OcrBoundingGeometry {
38986
39792
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
38987
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
39793
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
39794
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
38988
39795
  // For unit enums or when passed as a string, fall back to string-based conversion.
38989
39796
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
38990
39797
  s
@@ -39016,9 +39823,10 @@ impl magnus::TryConvert for OcrBoundingGeometry {
39016
39823
 
39017
39824
  unsafe impl IntoValueFromNative for OcrBoundingGeometry {}
39018
39825
  unsafe impl TryConvertOwned for OcrBoundingGeometry {}
39826
+
39019
39827
  impl OcrBoundingGeometry {}
39020
39828
 
39021
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
39829
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39022
39830
  #[serde(rename_all = "snake_case")]
39023
39831
  pub enum OcrElementLevel {
39024
39832
  Word,
@@ -39047,7 +39855,14 @@ impl magnus::IntoValue for OcrElementLevel {
39047
39855
 
39048
39856
  impl magnus::TryConvert for OcrElementLevel {
39049
39857
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39050
- let s: String = magnus::TryConvert::try_convert(val)?;
39858
+ // IntoValue emits Symbols, including when this enum is a Hash key.
39859
+ // Accept that exact representation as well as String inputs; do not
39860
+ // coerce arbitrary objects through to_s.
39861
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
39862
+ symbol.name()?.into_owned()
39863
+ } else {
39864
+ magnus::TryConvert::try_convert(val)?
39865
+ };
39051
39866
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39052
39867
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39053
39868
  // existing consumer code written in any of those styles keep working.
@@ -39092,7 +39907,8 @@ impl magnus::IntoValue for OcrStrategy {
39092
39907
 
39093
39908
  impl magnus::TryConvert for OcrStrategy {
39094
39909
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39095
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
39910
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
39911
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
39096
39912
  // For unit enums or when passed as a string, fall back to string-based conversion.
39097
39913
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
39098
39914
  s
@@ -39124,6 +39940,7 @@ impl magnus::TryConvert for OcrStrategy {
39124
39940
 
39125
39941
  unsafe impl IntoValueFromNative for OcrStrategy {}
39126
39942
  unsafe impl TryConvertOwned for OcrStrategy {}
39943
+
39127
39944
  impl OcrStrategy {}
39128
39945
 
39129
39946
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -39187,7 +40004,7 @@ impl magnus::TryConvert for OutputFormat {
39187
40004
  unsafe impl IntoValueFromNative for OutputFormat {}
39188
40005
  unsafe impl TryConvertOwned for OutputFormat {}
39189
40006
 
39190
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40007
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39191
40008
  pub enum PSMMode {
39192
40009
  OsdOnly,
39193
40010
  AutoOsd,
@@ -39229,7 +40046,14 @@ impl magnus::IntoValue for PSMMode {
39229
40046
 
39230
40047
  impl magnus::TryConvert for PSMMode {
39231
40048
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39232
- let s: String = magnus::TryConvert::try_convert(val)?;
40049
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40050
+ // Accept that exact representation as well as String inputs; do not
40051
+ // coerce arbitrary objects through to_s.
40052
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40053
+ symbol.name()?.into_owned()
40054
+ } else {
40055
+ magnus::TryConvert::try_convert(val)?
40056
+ };
39233
40057
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39234
40058
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39235
40059
  // existing consumer code written in any of those styles keep working.
@@ -39256,7 +40080,7 @@ impl magnus::TryConvert for PSMMode {
39256
40080
  unsafe impl IntoValueFromNative for PSMMode {}
39257
40081
  unsafe impl TryConvertOwned for PSMMode {}
39258
40082
 
39259
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40083
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39260
40084
  #[serde(rename_all = "snake_case")]
39261
40085
  pub enum PaddleInferenceBackend {
39262
40086
  Ort,
@@ -39281,7 +40105,14 @@ impl magnus::IntoValue for PaddleInferenceBackend {
39281
40105
 
39282
40106
  impl magnus::TryConvert for PaddleInferenceBackend {
39283
40107
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39284
- let s: String = magnus::TryConvert::try_convert(val)?;
40108
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40109
+ // Accept that exact representation as well as String inputs; do not
40110
+ // coerce arbitrary objects through to_s.
40111
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40112
+ symbol.name()?.into_owned()
40113
+ } else {
40114
+ magnus::TryConvert::try_convert(val)?
40115
+ };
39285
40116
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39286
40117
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39287
40118
  // existing consumer code written in any of those styles keep working.
@@ -39299,7 +40130,7 @@ impl magnus::TryConvert for PaddleInferenceBackend {
39299
40130
  unsafe impl IntoValueFromNative for PaddleInferenceBackend {}
39300
40131
  unsafe impl TryConvertOwned for PaddleInferenceBackend {}
39301
40132
 
39302
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40133
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39303
40134
  pub enum PaddleLanguage {
39304
40135
  English,
39305
40136
  Chinese,
@@ -39351,7 +40182,14 @@ impl magnus::IntoValue for PaddleLanguage {
39351
40182
 
39352
40183
  impl magnus::TryConvert for PaddleLanguage {
39353
40184
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39354
- let s: String = magnus::TryConvert::try_convert(val)?;
40185
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40186
+ // Accept that exact representation as well as String inputs; do not
40187
+ // coerce arbitrary objects through to_s.
40188
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40189
+ symbol.name()?.into_owned()
40190
+ } else {
40191
+ magnus::TryConvert::try_convert(val)?
40192
+ };
39355
40193
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39356
40194
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39357
40195
  // existing consumer code written in any of those styles keep working.
@@ -39383,7 +40221,7 @@ impl magnus::TryConvert for PaddleLanguage {
39383
40221
  unsafe impl IntoValueFromNative for PaddleLanguage {}
39384
40222
  unsafe impl TryConvertOwned for PaddleLanguage {}
39385
40223
 
39386
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40224
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39387
40225
  #[serde(rename_all = "lowercase")]
39388
40226
  pub enum PaddleOcrVlTaskKind {
39389
40227
  Ocr,
@@ -39412,7 +40250,14 @@ impl magnus::IntoValue for PaddleOcrVlTaskKind {
39412
40250
 
39413
40251
  impl magnus::TryConvert for PaddleOcrVlTaskKind {
39414
40252
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39415
- let s: String = magnus::TryConvert::try_convert(val)?;
40253
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40254
+ // Accept that exact representation as well as String inputs; do not
40255
+ // coerce arbitrary objects through to_s.
40256
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40257
+ symbol.name()?.into_owned()
40258
+ } else {
40259
+ magnus::TryConvert::try_convert(val)?
40260
+ };
39416
40261
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39417
40262
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39418
40263
  // existing consumer code written in any of those styles keep working.
@@ -39432,7 +40277,7 @@ impl magnus::TryConvert for PaddleOcrVlTaskKind {
39432
40277
  unsafe impl IntoValueFromNative for PaddleOcrVlTaskKind {}
39433
40278
  unsafe impl TryConvertOwned for PaddleOcrVlTaskKind {}
39434
40279
 
39435
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40280
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39436
40281
  pub enum PageOrientationHandling {
39437
40282
  SelfCorrecting,
39438
40283
  RecognisesRotatedText,
@@ -39458,7 +40303,14 @@ impl magnus::IntoValue for PageOrientationHandling {
39458
40303
 
39459
40304
  impl magnus::TryConvert for PageOrientationHandling {
39460
40305
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39461
- let s: String = magnus::TryConvert::try_convert(val)?;
40306
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40307
+ // Accept that exact representation as well as String inputs; do not
40308
+ // coerce arbitrary objects through to_s.
40309
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40310
+ symbol.name()?.into_owned()
40311
+ } else {
40312
+ magnus::TryConvert::try_convert(val)?
40313
+ };
39462
40314
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39463
40315
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39464
40316
  // existing consumer code written in any of those styles keep working.
@@ -39477,7 +40329,7 @@ impl magnus::TryConvert for PageOrientationHandling {
39477
40329
  unsafe impl IntoValueFromNative for PageOrientationHandling {}
39478
40330
  unsafe impl TryConvertOwned for PageOrientationHandling {}
39479
40331
 
39480
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40332
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39481
40333
  #[serde(rename_all = "snake_case")]
39482
40334
  pub enum PageUnitType {
39483
40335
  Page,
@@ -39504,7 +40356,14 @@ impl magnus::IntoValue for PageUnitType {
39504
40356
 
39505
40357
  impl magnus::TryConvert for PageUnitType {
39506
40358
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39507
- let s: String = magnus::TryConvert::try_convert(val)?;
40359
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40360
+ // Accept that exact representation as well as String inputs; do not
40361
+ // coerce arbitrary objects through to_s.
40362
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40363
+ symbol.name()?.into_owned()
40364
+ } else {
40365
+ magnus::TryConvert::try_convert(val)?
40366
+ };
39508
40367
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39509
40368
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39510
40369
  // existing consumer code written in any of those styles keep working.
@@ -39523,7 +40382,7 @@ impl magnus::TryConvert for PageUnitType {
39523
40382
  unsafe impl IntoValueFromNative for PageUnitType {}
39524
40383
  unsafe impl TryConvertOwned for PageUnitType {}
39525
40384
 
39526
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40385
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39527
40386
  #[serde(rename_all = "snake_case")]
39528
40387
  pub enum PdfAnnotationType {
39529
40388
  Text,
@@ -39580,7 +40439,14 @@ impl magnus::IntoValue for PdfAnnotationType {
39580
40439
 
39581
40440
  impl magnus::TryConvert for PdfAnnotationType {
39582
40441
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39583
- let s: String = magnus::TryConvert::try_convert(val)?;
40442
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40443
+ // Accept that exact representation as well as String inputs; do not
40444
+ // coerce arbitrary objects through to_s.
40445
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40446
+ symbol.name()?.into_owned()
40447
+ } else {
40448
+ magnus::TryConvert::try_convert(val)?
40449
+ };
39584
40450
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39585
40451
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39586
40452
  // existing consumer code written in any of those styles keep working.
@@ -39614,7 +40480,7 @@ impl magnus::TryConvert for PdfAnnotationType {
39614
40480
  unsafe impl IntoValueFromNative for PdfAnnotationType {}
39615
40481
  unsafe impl TryConvertOwned for PdfAnnotationType {}
39616
40482
 
39617
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40483
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39618
40484
  #[serde(rename_all = "snake_case")]
39619
40485
  pub enum PdfBackend {
39620
40486
  Native,
@@ -39639,7 +40505,14 @@ impl magnus::IntoValue for PdfBackend {
39639
40505
 
39640
40506
  impl magnus::TryConvert for PdfBackend {
39641
40507
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39642
- let s: String = magnus::TryConvert::try_convert(val)?;
40508
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40509
+ // Accept that exact representation as well as String inputs; do not
40510
+ // coerce arbitrary objects through to_s.
40511
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40512
+ symbol.name()?.into_owned()
40513
+ } else {
40514
+ magnus::TryConvert::try_convert(val)?
40515
+ };
39643
40516
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39644
40517
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39645
40518
  // existing consumer code written in any of those styles keep working.
@@ -39724,7 +40597,7 @@ impl magnus::TryConvert for PiiCategory {
39724
40597
  unsafe impl IntoValueFromNative for PiiCategory {}
39725
40598
  unsafe impl TryConvertOwned for PiiCategory {}
39726
40599
 
39727
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40600
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39728
40601
  pub enum PreprocessingPreset {
39729
40602
  Minimal,
39730
40603
  Standard,
@@ -39750,7 +40623,14 @@ impl magnus::IntoValue for PreprocessingPreset {
39750
40623
 
39751
40624
  impl magnus::TryConvert for PreprocessingPreset {
39752
40625
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39753
- let s: String = magnus::TryConvert::try_convert(val)?;
40626
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40627
+ // Accept that exact representation as well as String inputs; do not
40628
+ // coerce arbitrary objects through to_s.
40629
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40630
+ symbol.name()?.into_owned()
40631
+ } else {
40632
+ magnus::TryConvert::try_convert(val)?
40633
+ };
39754
40634
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39755
40635
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39756
40636
  // existing consumer code written in any of those styles keep working.
@@ -39769,7 +40649,7 @@ impl magnus::TryConvert for PreprocessingPreset {
39769
40649
  unsafe impl IntoValueFromNative for PreprocessingPreset {}
39770
40650
  unsafe impl TryConvertOwned for PreprocessingPreset {}
39771
40651
 
39772
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40652
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39773
40653
  #[serde(rename_all = "snake_case")]
39774
40654
  pub enum PresetCategory {
39775
40655
  Finance,
@@ -39804,7 +40684,14 @@ impl magnus::IntoValue for PresetCategory {
39804
40684
 
39805
40685
  impl magnus::TryConvert for PresetCategory {
39806
40686
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39807
- let s: String = magnus::TryConvert::try_convert(val)?;
40687
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40688
+ // Accept that exact representation as well as String inputs; do not
40689
+ // coerce arbitrary objects through to_s.
40690
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40691
+ symbol.name()?.into_owned()
40692
+ } else {
40693
+ magnus::TryConvert::try_convert(val)?
40694
+ };
39808
40695
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39809
40696
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39810
40697
  // existing consumer code written in any of those styles keep working.
@@ -39827,7 +40714,7 @@ impl magnus::TryConvert for PresetCategory {
39827
40714
  unsafe impl IntoValueFromNative for PresetCategory {}
39828
40715
  unsafe impl TryConvertOwned for PresetCategory {}
39829
40716
 
39830
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40717
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39831
40718
  #[serde(rename_all = "lowercase")]
39832
40719
  pub enum ProbeStatus {
39833
40720
  Pass,
@@ -39856,7 +40743,14 @@ impl magnus::IntoValue for ProbeStatus {
39856
40743
 
39857
40744
  impl magnus::TryConvert for ProbeStatus {
39858
40745
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39859
- let s: String = magnus::TryConvert::try_convert(val)?;
40746
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40747
+ // Accept that exact representation as well as String inputs; do not
40748
+ // coerce arbitrary objects through to_s.
40749
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40750
+ symbol.name()?.into_owned()
40751
+ } else {
40752
+ magnus::TryConvert::try_convert(val)?
40753
+ };
39860
40754
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39861
40755
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39862
40756
  // existing consumer code written in any of those styles keep working.
@@ -39876,7 +40770,7 @@ impl magnus::TryConvert for ProbeStatus {
39876
40770
  unsafe impl IntoValueFromNative for ProbeStatus {}
39877
40771
  unsafe impl TryConvertOwned for ProbeStatus {}
39878
40772
 
39879
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40773
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39880
40774
  pub enum ProcessingStage {
39881
40775
  Early,
39882
40776
  Middle,
@@ -39902,7 +40796,14 @@ impl magnus::IntoValue for ProcessingStage {
39902
40796
 
39903
40797
  impl magnus::TryConvert for ProcessingStage {
39904
40798
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39905
- let s: String = magnus::TryConvert::try_convert(val)?;
40799
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40800
+ // Accept that exact representation as well as String inputs; do not
40801
+ // coerce arbitrary objects through to_s.
40802
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40803
+ symbol.name()?.into_owned()
40804
+ } else {
40805
+ magnus::TryConvert::try_convert(val)?
40806
+ };
39906
40807
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39907
40808
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39908
40809
  // existing consumer code written in any of those styles keep working.
@@ -39921,7 +40822,7 @@ impl magnus::TryConvert for ProcessingStage {
39921
40822
  unsafe impl IntoValueFromNative for ProcessingStage {}
39922
40823
  unsafe impl TryConvertOwned for ProcessingStage {}
39923
40824
 
39924
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40825
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39925
40826
  #[serde(rename_all = "snake_case")]
39926
40827
  pub enum RedactionStrategy {
39927
40828
  Mask,
@@ -39950,7 +40851,14 @@ impl magnus::IntoValue for RedactionStrategy {
39950
40851
 
39951
40852
  impl magnus::TryConvert for RedactionStrategy {
39952
40853
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
39953
- let s: String = magnus::TryConvert::try_convert(val)?;
40854
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40855
+ // Accept that exact representation as well as String inputs; do not
40856
+ // coerce arbitrary objects through to_s.
40857
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40858
+ symbol.name()?.into_owned()
40859
+ } else {
40860
+ magnus::TryConvert::try_convert(val)?
40861
+ };
39954
40862
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
39955
40863
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
39956
40864
  // existing consumer code written in any of those styles keep working.
@@ -39970,7 +40878,7 @@ impl magnus::TryConvert for RedactionStrategy {
39970
40878
  unsafe impl IntoValueFromNative for RedactionStrategy {}
39971
40879
  unsafe impl TryConvertOwned for RedactionStrategy {}
39972
40880
 
39973
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40881
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
39974
40882
  pub enum ReductionLevel {
39975
40883
  Off,
39976
40884
  Light,
@@ -40000,7 +40908,14 @@ impl magnus::IntoValue for ReductionLevel {
40000
40908
 
40001
40909
  impl magnus::TryConvert for ReductionLevel {
40002
40910
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40003
- let s: String = magnus::TryConvert::try_convert(val)?;
40911
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40912
+ // Accept that exact representation as well as String inputs; do not
40913
+ // coerce arbitrary objects through to_s.
40914
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40915
+ symbol.name()?.into_owned()
40916
+ } else {
40917
+ magnus::TryConvert::try_convert(val)?
40918
+ };
40004
40919
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40005
40920
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40006
40921
  // existing consumer code written in any of those styles keep working.
@@ -40021,7 +40936,7 @@ impl magnus::TryConvert for ReductionLevel {
40021
40936
  unsafe impl IntoValueFromNative for ReductionLevel {}
40022
40937
  unsafe impl TryConvertOwned for ReductionLevel {}
40023
40938
 
40024
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40939
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40025
40940
  pub enum RegionKind {
40026
40941
  Figure,
40027
40942
  DenseTable,
@@ -40049,7 +40964,14 @@ impl magnus::IntoValue for RegionKind {
40049
40964
 
40050
40965
  impl magnus::TryConvert for RegionKind {
40051
40966
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40052
- let s: String = magnus::TryConvert::try_convert(val)?;
40967
+ // IntoValue emits Symbols, including when this enum is a Hash key.
40968
+ // Accept that exact representation as well as String inputs; do not
40969
+ // coerce arbitrary objects through to_s.
40970
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
40971
+ symbol.name()?.into_owned()
40972
+ } else {
40973
+ magnus::TryConvert::try_convert(val)?
40974
+ };
40053
40975
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40054
40976
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40055
40977
  // existing consumer code written in any of those styles keep working.
@@ -40069,7 +40991,7 @@ impl magnus::TryConvert for RegionKind {
40069
40991
  unsafe impl IntoValueFromNative for RegionKind {}
40070
40992
  unsafe impl TryConvertOwned for RegionKind {}
40071
40993
 
40072
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40994
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40073
40995
  #[serde(rename_all = "snake_case")]
40074
40996
  pub enum RelationshipKind {
40075
40997
  FootnoteReference,
@@ -40104,7 +41026,14 @@ impl magnus::IntoValue for RelationshipKind {
40104
41026
 
40105
41027
  impl magnus::TryConvert for RelationshipKind {
40106
41028
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40107
- let s: String = magnus::TryConvert::try_convert(val)?;
41029
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41030
+ // Accept that exact representation as well as String inputs; do not
41031
+ // coerce arbitrary objects through to_s.
41032
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41033
+ symbol.name()?.into_owned()
41034
+ } else {
41035
+ magnus::TryConvert::try_convert(val)?
41036
+ };
40108
41037
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40109
41038
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40110
41039
  // existing consumer code written in any of those styles keep working.
@@ -40127,7 +41056,7 @@ impl magnus::TryConvert for RelationshipKind {
40127
41056
  unsafe impl IntoValueFromNative for RelationshipKind {}
40128
41057
  unsafe impl TryConvertOwned for RelationshipKind {}
40129
41058
 
40130
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41059
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40131
41060
  #[serde(rename_all = "snake_case")]
40132
41061
  pub enum RerankerHead {
40133
41062
  CrossEncoder,
@@ -40152,7 +41081,14 @@ impl magnus::IntoValue for RerankerHead {
40152
41081
 
40153
41082
  impl magnus::TryConvert for RerankerHead {
40154
41083
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40155
- let s: String = magnus::TryConvert::try_convert(val)?;
41084
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41085
+ // Accept that exact representation as well as String inputs; do not
41086
+ // coerce arbitrary objects through to_s.
41087
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41088
+ symbol.name()?.into_owned()
41089
+ } else {
41090
+ magnus::TryConvert::try_convert(val)?
41091
+ };
40156
41092
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40157
41093
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40158
41094
  // existing consumer code written in any of those styles keep working.
@@ -40185,7 +41121,7 @@ pub enum RerankerModelType {
40185
41121
  head: RerankerHead,
40186
41122
  },
40187
41123
  Llm {
40188
- llm: LlmConfig,
41124
+ llm: Box<LlmConfig>,
40189
41125
  },
40190
41126
  Plugin {
40191
41127
  name: String,
@@ -40211,7 +41147,8 @@ impl magnus::IntoValue for RerankerModelType {
40211
41147
 
40212
41148
  impl magnus::TryConvert for RerankerModelType {
40213
41149
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40214
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
41150
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
41151
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
40215
41152
  // For unit enums or when passed as a string, fall back to string-based conversion.
40216
41153
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
40217
41154
  s
@@ -40243,9 +41180,10 @@ impl magnus::TryConvert for RerankerModelType {
40243
41180
 
40244
41181
  unsafe impl IntoValueFromNative for RerankerModelType {}
40245
41182
  unsafe impl TryConvertOwned for RerankerModelType {}
41183
+
40246
41184
  impl RerankerModelType {}
40247
41185
 
40248
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41186
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40249
41187
  #[serde(rename_all = "snake_case")]
40250
41188
  pub enum ResultFormat {
40251
41189
  Unified,
@@ -40270,7 +41208,14 @@ impl magnus::IntoValue for ResultFormat {
40270
41208
 
40271
41209
  impl magnus::TryConvert for ResultFormat {
40272
41210
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40273
- let s: String = magnus::TryConvert::try_convert(val)?;
41211
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41212
+ // Accept that exact representation as well as String inputs; do not
41213
+ // coerce arbitrary objects through to_s.
41214
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41215
+ symbol.name()?.into_owned()
41216
+ } else {
41217
+ magnus::TryConvert::try_convert(val)?
41218
+ };
40274
41219
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40275
41220
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40276
41221
  // existing consumer code written in any of those styles keep working.
@@ -40318,7 +41263,8 @@ impl magnus::IntoValue for RevisionAnchor {
40318
41263
 
40319
41264
  impl magnus::TryConvert for RevisionAnchor {
40320
41265
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40321
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
41266
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
41267
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
40322
41268
  // For unit enums or when passed as a string, fall back to string-based conversion.
40323
41269
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
40324
41270
  s
@@ -40350,9 +41296,10 @@ impl magnus::TryConvert for RevisionAnchor {
40350
41296
 
40351
41297
  unsafe impl IntoValueFromNative for RevisionAnchor {}
40352
41298
  unsafe impl TryConvertOwned for RevisionAnchor {}
41299
+
40353
41300
  impl RevisionAnchor {}
40354
41301
 
40355
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41302
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40356
41303
  #[serde(rename_all = "snake_case")]
40357
41304
  pub enum RevisionKind {
40358
41305
  Insertion,
@@ -40381,7 +41328,14 @@ impl magnus::IntoValue for RevisionKind {
40381
41328
 
40382
41329
  impl magnus::TryConvert for RevisionKind {
40383
41330
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40384
- let s: String = magnus::TryConvert::try_convert(val)?;
41331
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41332
+ // Accept that exact representation as well as String inputs; do not
41333
+ // coerce arbitrary objects through to_s.
41334
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41335
+ symbol.name()?.into_owned()
41336
+ } else {
41337
+ magnus::TryConvert::try_convert(val)?
41338
+ };
40385
41339
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40386
41340
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40387
41341
  // existing consumer code written in any of those styles keep working.
@@ -40401,7 +41355,7 @@ impl magnus::TryConvert for RevisionKind {
40401
41355
  unsafe impl IntoValueFromNative for RevisionKind {}
40402
41356
  unsafe impl TryConvertOwned for RevisionKind {}
40403
41357
 
40404
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41358
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40405
41359
  #[serde(rename_all = "snake_case")]
40406
41360
  pub enum SchemaCompliance {
40407
41361
  AllValid,
@@ -40428,7 +41382,14 @@ impl magnus::IntoValue for SchemaCompliance {
40428
41382
 
40429
41383
  impl magnus::TryConvert for SchemaCompliance {
40430
41384
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40431
- let s: String = magnus::TryConvert::try_convert(val)?;
41385
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41386
+ // Accept that exact representation as well as String inputs; do not
41387
+ // coerce arbitrary objects through to_s.
41388
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41389
+ symbol.name()?.into_owned()
41390
+ } else {
41391
+ magnus::TryConvert::try_convert(val)?
41392
+ };
40432
41393
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40433
41394
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40434
41395
  // existing consumer code written in any of those styles keep working.
@@ -40484,7 +41445,8 @@ impl magnus::IntoValue for SparseEmbeddingModelType {
40484
41445
 
40485
41446
  impl magnus::TryConvert for SparseEmbeddingModelType {
40486
41447
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40487
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
41448
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
41449
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
40488
41450
  // For unit enums or when passed as a string, fall back to string-based conversion.
40489
41451
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
40490
41452
  s
@@ -40516,9 +41478,10 @@ impl magnus::TryConvert for SparseEmbeddingModelType {
40516
41478
 
40517
41479
  unsafe impl IntoValueFromNative for SparseEmbeddingModelType {}
40518
41480
  unsafe impl TryConvertOwned for SparseEmbeddingModelType {}
41481
+
40519
41482
  impl SparseEmbeddingModelType {}
40520
41483
 
40521
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41484
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40522
41485
  #[serde(rename_all = "lowercase")]
40523
41486
  pub enum StructuredDataType {
40524
41487
  #[serde(rename = "json-ld")]
@@ -40547,7 +41510,14 @@ impl magnus::IntoValue for StructuredDataType {
40547
41510
 
40548
41511
  impl magnus::TryConvert for StructuredDataType {
40549
41512
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40550
- let s: String = magnus::TryConvert::try_convert(val)?;
41513
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41514
+ // Accept that exact representation as well as String inputs; do not
41515
+ // coerce arbitrary objects through to_s.
41516
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41517
+ symbol.name()?.into_owned()
41518
+ } else {
41519
+ magnus::TryConvert::try_convert(val)?
41520
+ };
40551
41521
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40552
41522
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40553
41523
  // existing consumer code written in any of those styles keep working.
@@ -40566,7 +41536,7 @@ impl magnus::TryConvert for StructuredDataType {
40566
41536
  unsafe impl IntoValueFromNative for StructuredDataType {}
40567
41537
  unsafe impl TryConvertOwned for StructuredDataType {}
40568
41538
 
40569
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41539
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40570
41540
  #[serde(rename_all = "snake_case")]
40571
41541
  pub enum SummaryStrategy {
40572
41542
  Extractive,
@@ -40591,7 +41561,14 @@ impl magnus::IntoValue for SummaryStrategy {
40591
41561
 
40592
41562
  impl magnus::TryConvert for SummaryStrategy {
40593
41563
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40594
- let s: String = magnus::TryConvert::try_convert(val)?;
41564
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41565
+ // Accept that exact representation as well as String inputs; do not
41566
+ // coerce arbitrary objects through to_s.
41567
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41568
+ symbol.name()?.into_owned()
41569
+ } else {
41570
+ magnus::TryConvert::try_convert(val)?
41571
+ };
40595
41572
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40596
41573
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40597
41574
  // existing consumer code written in any of those styles keep working.
@@ -40609,7 +41586,7 @@ impl magnus::TryConvert for SummaryStrategy {
40609
41586
  unsafe impl IntoValueFromNative for SummaryStrategy {}
40610
41587
  unsafe impl TryConvertOwned for SummaryStrategy {}
40611
41588
 
40612
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41589
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40613
41590
  #[serde(rename_all = "snake_case")]
40614
41591
  pub enum TableChunkingMode {
40615
41592
  Split,
@@ -40634,7 +41611,14 @@ impl magnus::IntoValue for TableChunkingMode {
40634
41611
 
40635
41612
  impl magnus::TryConvert for TableChunkingMode {
40636
41613
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40637
- let s: String = magnus::TryConvert::try_convert(val)?;
41614
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41615
+ // Accept that exact representation as well as String inputs; do not
41616
+ // coerce arbitrary objects through to_s.
41617
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41618
+ symbol.name()?.into_owned()
41619
+ } else {
41620
+ magnus::TryConvert::try_convert(val)?
41621
+ };
40638
41622
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40639
41623
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40640
41624
  // existing consumer code written in any of those styles keep working.
@@ -40652,7 +41636,7 @@ impl magnus::TryConvert for TableChunkingMode {
40652
41636
  unsafe impl IntoValueFromNative for TableChunkingMode {}
40653
41637
  unsafe impl TryConvertOwned for TableChunkingMode {}
40654
41638
 
40655
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41639
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40656
41640
  #[serde(rename_all = "snake_case")]
40657
41641
  pub enum TableModel {
40658
41642
  Tatr,
@@ -40685,7 +41669,14 @@ impl magnus::IntoValue for TableModel {
40685
41669
 
40686
41670
  impl magnus::TryConvert for TableModel {
40687
41671
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40688
- let s: String = magnus::TryConvert::try_convert(val)?;
41672
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41673
+ // Accept that exact representation as well as String inputs; do not
41674
+ // coerce arbitrary objects through to_s.
41675
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41676
+ symbol.name()?.into_owned()
41677
+ } else {
41678
+ magnus::TryConvert::try_convert(val)?
41679
+ };
40689
41680
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40690
41681
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40691
41682
  // existing consumer code written in any of those styles keep working.
@@ -40707,7 +41698,7 @@ impl magnus::TryConvert for TableModel {
40707
41698
  unsafe impl IntoValueFromNative for TableModel {}
40708
41699
  unsafe impl TryConvertOwned for TableModel {}
40709
41700
 
40710
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41701
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40711
41702
  #[serde(rename_all = "snake_case")]
40712
41703
  pub enum TableOverlapPreference {
40713
41704
  Content,
@@ -40734,7 +41725,14 @@ impl magnus::IntoValue for TableOverlapPreference {
40734
41725
 
40735
41726
  impl magnus::TryConvert for TableOverlapPreference {
40736
41727
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40737
- let s: String = magnus::TryConvert::try_convert(val)?;
41728
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41729
+ // Accept that exact representation as well as String inputs; do not
41730
+ // coerce arbitrary objects through to_s.
41731
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41732
+ symbol.name()?.into_owned()
41733
+ } else {
41734
+ magnus::TryConvert::try_convert(val)?
41735
+ };
40738
41736
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40739
41737
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40740
41738
  // existing consumer code written in any of those styles keep working.
@@ -40753,7 +41751,7 @@ impl magnus::TryConvert for TableOverlapPreference {
40753
41751
  unsafe impl IntoValueFromNative for TableOverlapPreference {}
40754
41752
  unsafe impl TryConvertOwned for TableOverlapPreference {}
40755
41753
 
40756
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41754
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40757
41755
  #[serde(rename_all = "lowercase")]
40758
41756
  pub enum TextDirection {
40759
41757
  #[serde(rename = "ltr")]
@@ -40783,7 +41781,14 @@ impl magnus::IntoValue for TextDirection {
40783
41781
 
40784
41782
  impl magnus::TryConvert for TextDirection {
40785
41783
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40786
- let s: String = magnus::TryConvert::try_convert(val)?;
41784
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41785
+ // Accept that exact representation as well as String inputs; do not
41786
+ // coerce arbitrary objects through to_s.
41787
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41788
+ symbol.name()?.into_owned()
41789
+ } else {
41790
+ magnus::TryConvert::try_convert(val)?
41791
+ };
40787
41792
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40788
41793
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40789
41794
  // existing consumer code written in any of those styles keep working.
@@ -40802,7 +41807,7 @@ impl magnus::TryConvert for TextDirection {
40802
41807
  unsafe impl IntoValueFromNative for TextDirection {}
40803
41808
  unsafe impl TryConvertOwned for TextDirection {}
40804
41809
 
40805
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41810
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40806
41811
  #[serde(rename_all = "snake_case")]
40807
41812
  pub enum TierStrategy {
40808
41813
  Auto,
@@ -40827,7 +41832,14 @@ impl magnus::IntoValue for TierStrategy {
40827
41832
 
40828
41833
  impl magnus::TryConvert for TierStrategy {
40829
41834
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40830
- let s: String = magnus::TryConvert::try_convert(val)?;
41835
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41836
+ // Accept that exact representation as well as String inputs; do not
41837
+ // coerce arbitrary objects through to_s.
41838
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41839
+ symbol.name()?.into_owned()
41840
+ } else {
41841
+ magnus::TryConvert::try_convert(val)?
41842
+ };
40831
41843
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40832
41844
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40833
41845
  // existing consumer code written in any of those styles keep working.
@@ -40845,7 +41857,7 @@ impl magnus::TryConvert for TierStrategy {
40845
41857
  unsafe impl IntoValueFromNative for TierStrategy {}
40846
41858
  unsafe impl TryConvertOwned for TierStrategy {}
40847
41859
 
40848
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41860
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40849
41861
  #[serde(rename_all = "snake_case")]
40850
41862
  pub enum UriKind {
40851
41863
  Hyperlink,
@@ -40878,7 +41890,14 @@ impl magnus::IntoValue for UriKind {
40878
41890
 
40879
41891
  impl magnus::TryConvert for UriKind {
40880
41892
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40881
- let s: String = magnus::TryConvert::try_convert(val)?;
41893
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41894
+ // Accept that exact representation as well as String inputs; do not
41895
+ // coerce arbitrary objects through to_s.
41896
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41897
+ symbol.name()?.into_owned()
41898
+ } else {
41899
+ magnus::TryConvert::try_convert(val)?
41900
+ };
40882
41901
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40883
41902
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40884
41903
  // existing consumer code written in any of those styles keep working.
@@ -40900,7 +41919,7 @@ impl magnus::TryConvert for UriKind {
40900
41919
  unsafe impl IntoValueFromNative for UriKind {}
40901
41920
  unsafe impl TryConvertOwned for UriKind {}
40902
41921
 
40903
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41922
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40904
41923
  pub enum UrlEscapeStyle {
40905
41924
  Angle,
40906
41925
  Percent,
@@ -40924,7 +41943,14 @@ impl magnus::IntoValue for UrlEscapeStyle {
40924
41943
 
40925
41944
  impl magnus::TryConvert for UrlEscapeStyle {
40926
41945
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40927
- let s: String = magnus::TryConvert::try_convert(val)?;
41946
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41947
+ // Accept that exact representation as well as String inputs; do not
41948
+ // coerce arbitrary objects through to_s.
41949
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
41950
+ symbol.name()?.into_owned()
41951
+ } else {
41952
+ magnus::TryConvert::try_convert(val)?
41953
+ };
40928
41954
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40929
41955
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40930
41956
  // existing consumer code written in any of those styles keep working.
@@ -40942,7 +41968,7 @@ impl magnus::TryConvert for UrlEscapeStyle {
40942
41968
  unsafe impl IntoValueFromNative for UrlEscapeStyle {}
40943
41969
  unsafe impl TryConvertOwned for UrlEscapeStyle {}
40944
41970
 
40945
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
41971
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40946
41972
  #[serde(rename_all = "snake_case")]
40947
41973
  pub enum UrlExtractionMode {
40948
41974
  Auto,
@@ -40969,7 +41995,14 @@ impl magnus::IntoValue for UrlExtractionMode {
40969
41995
 
40970
41996
  impl magnus::TryConvert for UrlExtractionMode {
40971
41997
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
40972
- let s: String = magnus::TryConvert::try_convert(val)?;
41998
+ // IntoValue emits Symbols, including when this enum is a Hash key.
41999
+ // Accept that exact representation as well as String inputs; do not
42000
+ // coerce arbitrary objects through to_s.
42001
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
42002
+ symbol.name()?.into_owned()
42003
+ } else {
42004
+ magnus::TryConvert::try_convert(val)?
42005
+ };
40973
42006
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
40974
42007
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
40975
42008
  // existing consumer code written in any of those styles keep working.
@@ -41014,7 +42047,8 @@ impl magnus::IntoValue for VlmFallbackPolicy {
41014
42047
 
41015
42048
  impl magnus::TryConvert for VlmFallbackPolicy {
41016
42049
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
41017
- // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
42050
+ // Generated Data variants carry native DTOs, not JSON-compatible display strings.
42051
+ let class_name: Option<String> = val.class().funcall("name", ())?; // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
41018
42052
  // For unit enums or when passed as a string, fall back to string-based conversion.
41019
42053
  let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
41020
42054
  s
@@ -41046,9 +42080,10 @@ impl magnus::TryConvert for VlmFallbackPolicy {
41046
42080
 
41047
42081
  unsafe impl IntoValueFromNative for VlmFallbackPolicy {}
41048
42082
  unsafe impl TryConvertOwned for VlmFallbackPolicy {}
42083
+
41049
42084
  impl VlmFallbackPolicy {}
41050
42085
 
41051
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
42086
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
41052
42087
  #[serde(rename_all = "snake_case")]
41053
42088
  pub enum WhisperModel {
41054
42089
  Tiny,
@@ -41079,7 +42114,14 @@ impl magnus::IntoValue for WhisperModel {
41079
42114
 
41080
42115
  impl magnus::TryConvert for WhisperModel {
41081
42116
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
41082
- let s: String = magnus::TryConvert::try_convert(val)?;
42117
+ // IntoValue emits Symbols, including when this enum is a Hash key.
42118
+ // Accept that exact representation as well as String inputs; do not
42119
+ // coerce arbitrary objects through to_s.
42120
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
42121
+ symbol.name()?.into_owned()
42122
+ } else {
42123
+ magnus::TryConvert::try_convert(val)?
42124
+ };
41083
42125
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
41084
42126
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
41085
42127
  // existing consumer code written in any of those styles keep working.
@@ -41100,7 +42142,7 @@ impl magnus::TryConvert for WhisperModel {
41100
42142
  unsafe impl IntoValueFromNative for WhisperModel {}
41101
42143
  unsafe impl TryConvertOwned for WhisperModel {}
41102
42144
 
41103
- #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
42145
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
41104
42146
  pub enum WhitespaceMode {
41105
42147
  Normalized,
41106
42148
  Strict,
@@ -41124,7 +42166,14 @@ impl magnus::IntoValue for WhitespaceMode {
41124
42166
 
41125
42167
  impl magnus::TryConvert for WhitespaceMode {
41126
42168
  fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
41127
- let s: String = magnus::TryConvert::try_convert(val)?;
42169
+ // IntoValue emits Symbols, including when this enum is a Hash key.
42170
+ // Accept that exact representation as well as String inputs; do not
42171
+ // coerce arbitrary objects through to_s.
42172
+ let s: String = if let Some(symbol) = magnus::Symbol::from_value(val) {
42173
+ symbol.name()?.into_owned()
42174
+ } else {
42175
+ magnus::TryConvert::try_convert(val)?
42176
+ };
41128
42177
  // Accept the real serde wire value, the legacy always-snake_case symbol Magnus used to
41129
42178
  // emit unconditionally, and the verbatim PascalCase Rust variant name, so fixtures and
41130
42179
  // existing consumer code written in any of those styles keep working.
@@ -41615,7 +42664,7 @@ impl RbOcrBackendBridgeRuntimeDispatcher {
41615
42664
  let (sender, receiver) = std::sync::mpsc::sync_channel(Self::QUEUE_CAPACITY);
41616
42665
  let inner = magnus::value::Opaque::from(rb_obj);
41617
42666
  let thread = ruby.thread_create_from_fn(move |ruby| {
41618
- while let Ok(job) = Self::recv_without_gvl(&receiver) {
42667
+ while let Ok(job) = Self::recv_without_gvl(ruby, &receiver) {
41619
42668
  let value = inner.get_inner_with(ruby);
41620
42669
  let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
41621
42670
  job(ruby, value);
@@ -41648,33 +42697,47 @@ impl RbOcrBackendBridgeRuntimeDispatcher {
41648
42697
  }
41649
42698
 
41650
42699
  fn recv_without_gvl(
42700
+ ruby: &magnus::Ruby,
41651
42701
  receiver: &std::sync::mpsc::Receiver<RbOcrBackendBridgeRuntimeJob>,
41652
42702
  ) -> Result<RbOcrBackendBridgeRuntimeJob, std::sync::mpsc::RecvError> {
41653
42703
  struct ReceiveState<'a> {
41654
42704
  receiver: &'a std::sync::mpsc::Receiver<RbOcrBackendBridgeRuntimeJob>,
41655
- result: Option<Result<RbOcrBackendBridgeRuntimeJob, std::sync::mpsc::RecvError>>,
42705
+ result: Option<Result<RbOcrBackendBridgeRuntimeJob, std::sync::mpsc::RecvTimeoutError>>,
41656
42706
  }
41657
42707
 
41658
42708
  unsafe extern "C" fn receive(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
41659
42709
  // SAFETY: the state pointer remains valid until Ruby returns from the
41660
42710
  // without-GVL callback on this same Ruby-created thread.
41661
42711
  let state = unsafe { &mut *(data as *mut ReceiveState<'_>) };
41662
- state.result = Some(state.receiver.recv());
42712
+ state.result = Some(state.receiver.recv_timeout(std::time::Duration::from_millis(50)));
41663
42713
  std::ptr::null_mut()
41664
42714
  }
41665
42715
 
41666
- let mut state = ReceiveState { receiver, result: None };
41667
- // SAFETY: this runs on the dedicated Ruby-created dispatcher thread. The
41668
- // callback touches only Rust channel state and the sender disconnect wakes it.
41669
- unsafe {
41670
- rb_sys::rb_thread_call_without_gvl(
41671
- Some(receive),
41672
- &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
41673
- None,
41674
- std::ptr::null_mut(),
41675
- );
42716
+ loop {
42717
+ let mut state = ReceiveState { receiver, result: None };
42718
+ // SAFETY: the callback only touches Rust channel state, and returns
42719
+ // within 50ms even if a registered sender survives interpreter exit.
42720
+ // The gvl2 API defers interrupts: process them through Magnus below
42721
+ // so a Ruby non-local exit cannot skip Rust stack destructors here.
42722
+ unsafe {
42723
+ rb_sys::rb_thread_call_without_gvl2(
42724
+ Some(receive),
42725
+ &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
42726
+ None,
42727
+ std::ptr::null_mut(),
42728
+ );
42729
+ }
42730
+ if ruby.thread_check_ints().is_err() {
42731
+ return Err(std::sync::mpsc::RecvError);
42732
+ }
42733
+ match state.result {
42734
+ Some(Ok(job)) => return Ok(job),
42735
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Disconnected)) => {
42736
+ return Err(std::sync::mpsc::RecvError);
42737
+ }
42738
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Timeout)) | None => continue,
42739
+ }
41676
42740
  }
41677
- state.result.unwrap_or_else(|| Err(std::sync::mpsc::RecvError))
41678
42741
  }
41679
42742
  }
41680
42743
 
@@ -42898,7 +43961,7 @@ impl RbPostProcessorBridgeRuntimeDispatcher {
42898
43961
  let (sender, receiver) = std::sync::mpsc::sync_channel(Self::QUEUE_CAPACITY);
42899
43962
  let inner = magnus::value::Opaque::from(rb_obj);
42900
43963
  let thread = ruby.thread_create_from_fn(move |ruby| {
42901
- while let Ok(job) = Self::recv_without_gvl(&receiver) {
43964
+ while let Ok(job) = Self::recv_without_gvl(ruby, &receiver) {
42902
43965
  let value = inner.get_inner_with(ruby);
42903
43966
  let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
42904
43967
  job(ruby, value);
@@ -42931,33 +43994,47 @@ impl RbPostProcessorBridgeRuntimeDispatcher {
42931
43994
  }
42932
43995
 
42933
43996
  fn recv_without_gvl(
43997
+ ruby: &magnus::Ruby,
42934
43998
  receiver: &std::sync::mpsc::Receiver<RbPostProcessorBridgeRuntimeJob>,
42935
43999
  ) -> Result<RbPostProcessorBridgeRuntimeJob, std::sync::mpsc::RecvError> {
42936
44000
  struct ReceiveState<'a> {
42937
44001
  receiver: &'a std::sync::mpsc::Receiver<RbPostProcessorBridgeRuntimeJob>,
42938
- result: Option<Result<RbPostProcessorBridgeRuntimeJob, std::sync::mpsc::RecvError>>,
44002
+ result: Option<Result<RbPostProcessorBridgeRuntimeJob, std::sync::mpsc::RecvTimeoutError>>,
42939
44003
  }
42940
44004
 
42941
44005
  unsafe extern "C" fn receive(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
42942
44006
  // SAFETY: the state pointer remains valid until Ruby returns from the
42943
44007
  // without-GVL callback on this same Ruby-created thread.
42944
44008
  let state = unsafe { &mut *(data as *mut ReceiveState<'_>) };
42945
- state.result = Some(state.receiver.recv());
44009
+ state.result = Some(state.receiver.recv_timeout(std::time::Duration::from_millis(50)));
42946
44010
  std::ptr::null_mut()
42947
44011
  }
42948
44012
 
42949
- let mut state = ReceiveState { receiver, result: None };
42950
- // SAFETY: this runs on the dedicated Ruby-created dispatcher thread. The
42951
- // callback touches only Rust channel state and the sender disconnect wakes it.
42952
- unsafe {
42953
- rb_sys::rb_thread_call_without_gvl(
42954
- Some(receive),
42955
- &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
42956
- None,
42957
- std::ptr::null_mut(),
42958
- );
44013
+ loop {
44014
+ let mut state = ReceiveState { receiver, result: None };
44015
+ // SAFETY: the callback only touches Rust channel state, and returns
44016
+ // within 50ms even if a registered sender survives interpreter exit.
44017
+ // The gvl2 API defers interrupts: process them through Magnus below
44018
+ // so a Ruby non-local exit cannot skip Rust stack destructors here.
44019
+ unsafe {
44020
+ rb_sys::rb_thread_call_without_gvl2(
44021
+ Some(receive),
44022
+ &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
44023
+ None,
44024
+ std::ptr::null_mut(),
44025
+ );
44026
+ }
44027
+ if ruby.thread_check_ints().is_err() {
44028
+ return Err(std::sync::mpsc::RecvError);
44029
+ }
44030
+ match state.result {
44031
+ Some(Ok(job)) => return Ok(job),
44032
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Disconnected)) => {
44033
+ return Err(std::sync::mpsc::RecvError);
44034
+ }
44035
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Timeout)) | None => continue,
44036
+ }
42959
44037
  }
42960
- state.result.unwrap_or_else(|| Err(std::sync::mpsc::RecvError))
42961
44038
  }
42962
44039
  }
42963
44040
 
@@ -43494,7 +44571,7 @@ impl RbValidatorBridgeRuntimeDispatcher {
43494
44571
  let (sender, receiver) = std::sync::mpsc::sync_channel(Self::QUEUE_CAPACITY);
43495
44572
  let inner = magnus::value::Opaque::from(rb_obj);
43496
44573
  let thread = ruby.thread_create_from_fn(move |ruby| {
43497
- while let Ok(job) = Self::recv_without_gvl(&receiver) {
44574
+ while let Ok(job) = Self::recv_without_gvl(ruby, &receiver) {
43498
44575
  let value = inner.get_inner_with(ruby);
43499
44576
  let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
43500
44577
  job(ruby, value);
@@ -43527,33 +44604,47 @@ impl RbValidatorBridgeRuntimeDispatcher {
43527
44604
  }
43528
44605
 
43529
44606
  fn recv_without_gvl(
44607
+ ruby: &magnus::Ruby,
43530
44608
  receiver: &std::sync::mpsc::Receiver<RbValidatorBridgeRuntimeJob>,
43531
44609
  ) -> Result<RbValidatorBridgeRuntimeJob, std::sync::mpsc::RecvError> {
43532
44610
  struct ReceiveState<'a> {
43533
44611
  receiver: &'a std::sync::mpsc::Receiver<RbValidatorBridgeRuntimeJob>,
43534
- result: Option<Result<RbValidatorBridgeRuntimeJob, std::sync::mpsc::RecvError>>,
44612
+ result: Option<Result<RbValidatorBridgeRuntimeJob, std::sync::mpsc::RecvTimeoutError>>,
43535
44613
  }
43536
44614
 
43537
44615
  unsafe extern "C" fn receive(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
43538
44616
  // SAFETY: the state pointer remains valid until Ruby returns from the
43539
44617
  // without-GVL callback on this same Ruby-created thread.
43540
44618
  let state = unsafe { &mut *(data as *mut ReceiveState<'_>) };
43541
- state.result = Some(state.receiver.recv());
44619
+ state.result = Some(state.receiver.recv_timeout(std::time::Duration::from_millis(50)));
43542
44620
  std::ptr::null_mut()
43543
44621
  }
43544
44622
 
43545
- let mut state = ReceiveState { receiver, result: None };
43546
- // SAFETY: this runs on the dedicated Ruby-created dispatcher thread. The
43547
- // callback touches only Rust channel state and the sender disconnect wakes it.
43548
- unsafe {
43549
- rb_sys::rb_thread_call_without_gvl(
43550
- Some(receive),
43551
- &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
43552
- None,
43553
- std::ptr::null_mut(),
43554
- );
44623
+ loop {
44624
+ let mut state = ReceiveState { receiver, result: None };
44625
+ // SAFETY: the callback only touches Rust channel state, and returns
44626
+ // within 50ms even if a registered sender survives interpreter exit.
44627
+ // The gvl2 API defers interrupts: process them through Magnus below
44628
+ // so a Ruby non-local exit cannot skip Rust stack destructors here.
44629
+ unsafe {
44630
+ rb_sys::rb_thread_call_without_gvl2(
44631
+ Some(receive),
44632
+ &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
44633
+ None,
44634
+ std::ptr::null_mut(),
44635
+ );
44636
+ }
44637
+ if ruby.thread_check_ints().is_err() {
44638
+ return Err(std::sync::mpsc::RecvError);
44639
+ }
44640
+ match state.result {
44641
+ Some(Ok(job)) => return Ok(job),
44642
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Disconnected)) => {
44643
+ return Err(std::sync::mpsc::RecvError);
44644
+ }
44645
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Timeout)) | None => continue,
44646
+ }
43555
44647
  }
43556
- state.result.unwrap_or_else(|| Err(std::sync::mpsc::RecvError))
43557
44648
  }
43558
44649
  }
43559
44650
 
@@ -43951,7 +45042,7 @@ impl RbDocumentExtractorBridgeRuntimeDispatcher {
43951
45042
  let (sender, receiver) = std::sync::mpsc::sync_channel(Self::QUEUE_CAPACITY);
43952
45043
  let inner = magnus::value::Opaque::from(rb_obj);
43953
45044
  let thread = ruby.thread_create_from_fn(move |ruby| {
43954
- while let Ok(job) = Self::recv_without_gvl(&receiver) {
45045
+ while let Ok(job) = Self::recv_without_gvl(ruby, &receiver) {
43955
45046
  let value = inner.get_inner_with(ruby);
43956
45047
  let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
43957
45048
  job(ruby, value);
@@ -43984,33 +45075,47 @@ impl RbDocumentExtractorBridgeRuntimeDispatcher {
43984
45075
  }
43985
45076
 
43986
45077
  fn recv_without_gvl(
45078
+ ruby: &magnus::Ruby,
43987
45079
  receiver: &std::sync::mpsc::Receiver<RbDocumentExtractorBridgeRuntimeJob>,
43988
45080
  ) -> Result<RbDocumentExtractorBridgeRuntimeJob, std::sync::mpsc::RecvError> {
43989
45081
  struct ReceiveState<'a> {
43990
45082
  receiver: &'a std::sync::mpsc::Receiver<RbDocumentExtractorBridgeRuntimeJob>,
43991
- result: Option<Result<RbDocumentExtractorBridgeRuntimeJob, std::sync::mpsc::RecvError>>,
45083
+ result: Option<Result<RbDocumentExtractorBridgeRuntimeJob, std::sync::mpsc::RecvTimeoutError>>,
43992
45084
  }
43993
45085
 
43994
45086
  unsafe extern "C" fn receive(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
43995
45087
  // SAFETY: the state pointer remains valid until Ruby returns from the
43996
45088
  // without-GVL callback on this same Ruby-created thread.
43997
45089
  let state = unsafe { &mut *(data as *mut ReceiveState<'_>) };
43998
- state.result = Some(state.receiver.recv());
45090
+ state.result = Some(state.receiver.recv_timeout(std::time::Duration::from_millis(50)));
43999
45091
  std::ptr::null_mut()
44000
45092
  }
44001
45093
 
44002
- let mut state = ReceiveState { receiver, result: None };
44003
- // SAFETY: this runs on the dedicated Ruby-created dispatcher thread. The
44004
- // callback touches only Rust channel state and the sender disconnect wakes it.
44005
- unsafe {
44006
- rb_sys::rb_thread_call_without_gvl(
44007
- Some(receive),
44008
- &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
44009
- None,
44010
- std::ptr::null_mut(),
44011
- );
45094
+ loop {
45095
+ let mut state = ReceiveState { receiver, result: None };
45096
+ // SAFETY: the callback only touches Rust channel state, and returns
45097
+ // within 50ms even if a registered sender survives interpreter exit.
45098
+ // The gvl2 API defers interrupts: process them through Magnus below
45099
+ // so a Ruby non-local exit cannot skip Rust stack destructors here.
45100
+ unsafe {
45101
+ rb_sys::rb_thread_call_without_gvl2(
45102
+ Some(receive),
45103
+ &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
45104
+ None,
45105
+ std::ptr::null_mut(),
45106
+ );
45107
+ }
45108
+ if ruby.thread_check_ints().is_err() {
45109
+ return Err(std::sync::mpsc::RecvError);
45110
+ }
45111
+ match state.result {
45112
+ Some(Ok(job)) => return Ok(job),
45113
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Disconnected)) => {
45114
+ return Err(std::sync::mpsc::RecvError);
45115
+ }
45116
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Timeout)) | None => continue,
45117
+ }
44012
45118
  }
44013
- state.result.unwrap_or_else(|| Err(std::sync::mpsc::RecvError))
44014
45119
  }
44015
45120
  }
44016
45121
 
@@ -44453,7 +45558,7 @@ impl RbEmbeddingBackendBridgeRuntimeDispatcher {
44453
45558
  let (sender, receiver) = std::sync::mpsc::sync_channel(Self::QUEUE_CAPACITY);
44454
45559
  let inner = magnus::value::Opaque::from(rb_obj);
44455
45560
  let thread = ruby.thread_create_from_fn(move |ruby| {
44456
- while let Ok(job) = Self::recv_without_gvl(&receiver) {
45561
+ while let Ok(job) = Self::recv_without_gvl(ruby, &receiver) {
44457
45562
  let value = inner.get_inner_with(ruby);
44458
45563
  let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
44459
45564
  job(ruby, value);
@@ -44486,33 +45591,47 @@ impl RbEmbeddingBackendBridgeRuntimeDispatcher {
44486
45591
  }
44487
45592
 
44488
45593
  fn recv_without_gvl(
45594
+ ruby: &magnus::Ruby,
44489
45595
  receiver: &std::sync::mpsc::Receiver<RbEmbeddingBackendBridgeRuntimeJob>,
44490
45596
  ) -> Result<RbEmbeddingBackendBridgeRuntimeJob, std::sync::mpsc::RecvError> {
44491
45597
  struct ReceiveState<'a> {
44492
45598
  receiver: &'a std::sync::mpsc::Receiver<RbEmbeddingBackendBridgeRuntimeJob>,
44493
- result: Option<Result<RbEmbeddingBackendBridgeRuntimeJob, std::sync::mpsc::RecvError>>,
45599
+ result: Option<Result<RbEmbeddingBackendBridgeRuntimeJob, std::sync::mpsc::RecvTimeoutError>>,
44494
45600
  }
44495
45601
 
44496
45602
  unsafe extern "C" fn receive(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
44497
45603
  // SAFETY: the state pointer remains valid until Ruby returns from the
44498
45604
  // without-GVL callback on this same Ruby-created thread.
44499
45605
  let state = unsafe { &mut *(data as *mut ReceiveState<'_>) };
44500
- state.result = Some(state.receiver.recv());
45606
+ state.result = Some(state.receiver.recv_timeout(std::time::Duration::from_millis(50)));
44501
45607
  std::ptr::null_mut()
44502
45608
  }
44503
45609
 
44504
- let mut state = ReceiveState { receiver, result: None };
44505
- // SAFETY: this runs on the dedicated Ruby-created dispatcher thread. The
44506
- // callback touches only Rust channel state and the sender disconnect wakes it.
44507
- unsafe {
44508
- rb_sys::rb_thread_call_without_gvl(
44509
- Some(receive),
44510
- &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
44511
- None,
44512
- std::ptr::null_mut(),
44513
- );
45610
+ loop {
45611
+ let mut state = ReceiveState { receiver, result: None };
45612
+ // SAFETY: the callback only touches Rust channel state, and returns
45613
+ // within 50ms even if a registered sender survives interpreter exit.
45614
+ // The gvl2 API defers interrupts: process them through Magnus below
45615
+ // so a Ruby non-local exit cannot skip Rust stack destructors here.
45616
+ unsafe {
45617
+ rb_sys::rb_thread_call_without_gvl2(
45618
+ Some(receive),
45619
+ &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
45620
+ None,
45621
+ std::ptr::null_mut(),
45622
+ );
45623
+ }
45624
+ if ruby.thread_check_ints().is_err() {
45625
+ return Err(std::sync::mpsc::RecvError);
45626
+ }
45627
+ match state.result {
45628
+ Some(Ok(job)) => return Ok(job),
45629
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Disconnected)) => {
45630
+ return Err(std::sync::mpsc::RecvError);
45631
+ }
45632
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Timeout)) | None => continue,
45633
+ }
44514
45634
  }
44515
- state.result.unwrap_or_else(|| Err(std::sync::mpsc::RecvError))
44516
45635
  }
44517
45636
  }
44518
45637
 
@@ -44780,7 +45899,7 @@ impl RbRendererBridgeRuntimeDispatcher {
44780
45899
  let (sender, receiver) = std::sync::mpsc::sync_channel(Self::QUEUE_CAPACITY);
44781
45900
  let inner = magnus::value::Opaque::from(rb_obj);
44782
45901
  let thread = ruby.thread_create_from_fn(move |ruby| {
44783
- while let Ok(job) = Self::recv_without_gvl(&receiver) {
45902
+ while let Ok(job) = Self::recv_without_gvl(ruby, &receiver) {
44784
45903
  let value = inner.get_inner_with(ruby);
44785
45904
  let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
44786
45905
  job(ruby, value);
@@ -44813,33 +45932,47 @@ impl RbRendererBridgeRuntimeDispatcher {
44813
45932
  }
44814
45933
 
44815
45934
  fn recv_without_gvl(
45935
+ ruby: &magnus::Ruby,
44816
45936
  receiver: &std::sync::mpsc::Receiver<RbRendererBridgeRuntimeJob>,
44817
45937
  ) -> Result<RbRendererBridgeRuntimeJob, std::sync::mpsc::RecvError> {
44818
45938
  struct ReceiveState<'a> {
44819
45939
  receiver: &'a std::sync::mpsc::Receiver<RbRendererBridgeRuntimeJob>,
44820
- result: Option<Result<RbRendererBridgeRuntimeJob, std::sync::mpsc::RecvError>>,
45940
+ result: Option<Result<RbRendererBridgeRuntimeJob, std::sync::mpsc::RecvTimeoutError>>,
44821
45941
  }
44822
45942
 
44823
45943
  unsafe extern "C" fn receive(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
44824
45944
  // SAFETY: the state pointer remains valid until Ruby returns from the
44825
45945
  // without-GVL callback on this same Ruby-created thread.
44826
45946
  let state = unsafe { &mut *(data as *mut ReceiveState<'_>) };
44827
- state.result = Some(state.receiver.recv());
45947
+ state.result = Some(state.receiver.recv_timeout(std::time::Duration::from_millis(50)));
44828
45948
  std::ptr::null_mut()
44829
45949
  }
44830
45950
 
44831
- let mut state = ReceiveState { receiver, result: None };
44832
- // SAFETY: this runs on the dedicated Ruby-created dispatcher thread. The
44833
- // callback touches only Rust channel state and the sender disconnect wakes it.
44834
- unsafe {
44835
- rb_sys::rb_thread_call_without_gvl(
44836
- Some(receive),
44837
- &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
44838
- None,
44839
- std::ptr::null_mut(),
44840
- );
45951
+ loop {
45952
+ let mut state = ReceiveState { receiver, result: None };
45953
+ // SAFETY: the callback only touches Rust channel state, and returns
45954
+ // within 50ms even if a registered sender survives interpreter exit.
45955
+ // The gvl2 API defers interrupts: process them through Magnus below
45956
+ // so a Ruby non-local exit cannot skip Rust stack destructors here.
45957
+ unsafe {
45958
+ rb_sys::rb_thread_call_without_gvl2(
45959
+ Some(receive),
45960
+ &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
45961
+ None,
45962
+ std::ptr::null_mut(),
45963
+ );
45964
+ }
45965
+ if ruby.thread_check_ints().is_err() {
45966
+ return Err(std::sync::mpsc::RecvError);
45967
+ }
45968
+ match state.result {
45969
+ Some(Ok(job)) => return Ok(job),
45970
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Disconnected)) => {
45971
+ return Err(std::sync::mpsc::RecvError);
45972
+ }
45973
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Timeout)) | None => continue,
45974
+ }
44841
45975
  }
44842
- state.result.unwrap_or_else(|| Err(std::sync::mpsc::RecvError))
44843
45976
  }
44844
45977
  }
44845
45978
 
@@ -45083,7 +46216,7 @@ impl RbRerankerBackendBridgeRuntimeDispatcher {
45083
46216
  let (sender, receiver) = std::sync::mpsc::sync_channel(Self::QUEUE_CAPACITY);
45084
46217
  let inner = magnus::value::Opaque::from(rb_obj);
45085
46218
  let thread = ruby.thread_create_from_fn(move |ruby| {
45086
- while let Ok(job) = Self::recv_without_gvl(&receiver) {
46219
+ while let Ok(job) = Self::recv_without_gvl(ruby, &receiver) {
45087
46220
  let value = inner.get_inner_with(ruby);
45088
46221
  let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
45089
46222
  job(ruby, value);
@@ -45116,33 +46249,47 @@ impl RbRerankerBackendBridgeRuntimeDispatcher {
45116
46249
  }
45117
46250
 
45118
46251
  fn recv_without_gvl(
46252
+ ruby: &magnus::Ruby,
45119
46253
  receiver: &std::sync::mpsc::Receiver<RbRerankerBackendBridgeRuntimeJob>,
45120
46254
  ) -> Result<RbRerankerBackendBridgeRuntimeJob, std::sync::mpsc::RecvError> {
45121
46255
  struct ReceiveState<'a> {
45122
46256
  receiver: &'a std::sync::mpsc::Receiver<RbRerankerBackendBridgeRuntimeJob>,
45123
- result: Option<Result<RbRerankerBackendBridgeRuntimeJob, std::sync::mpsc::RecvError>>,
46257
+ result: Option<Result<RbRerankerBackendBridgeRuntimeJob, std::sync::mpsc::RecvTimeoutError>>,
45124
46258
  }
45125
46259
 
45126
46260
  unsafe extern "C" fn receive(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
45127
46261
  // SAFETY: the state pointer remains valid until Ruby returns from the
45128
46262
  // without-GVL callback on this same Ruby-created thread.
45129
46263
  let state = unsafe { &mut *(data as *mut ReceiveState<'_>) };
45130
- state.result = Some(state.receiver.recv());
46264
+ state.result = Some(state.receiver.recv_timeout(std::time::Duration::from_millis(50)));
45131
46265
  std::ptr::null_mut()
45132
46266
  }
45133
46267
 
45134
- let mut state = ReceiveState { receiver, result: None };
45135
- // SAFETY: this runs on the dedicated Ruby-created dispatcher thread. The
45136
- // callback touches only Rust channel state and the sender disconnect wakes it.
45137
- unsafe {
45138
- rb_sys::rb_thread_call_without_gvl(
45139
- Some(receive),
45140
- &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
45141
- None,
45142
- std::ptr::null_mut(),
45143
- );
46268
+ loop {
46269
+ let mut state = ReceiveState { receiver, result: None };
46270
+ // SAFETY: the callback only touches Rust channel state, and returns
46271
+ // within 50ms even if a registered sender survives interpreter exit.
46272
+ // The gvl2 API defers interrupts: process them through Magnus below
46273
+ // so a Ruby non-local exit cannot skip Rust stack destructors here.
46274
+ unsafe {
46275
+ rb_sys::rb_thread_call_without_gvl2(
46276
+ Some(receive),
46277
+ &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
46278
+ None,
46279
+ std::ptr::null_mut(),
46280
+ );
46281
+ }
46282
+ if ruby.thread_check_ints().is_err() {
46283
+ return Err(std::sync::mpsc::RecvError);
46284
+ }
46285
+ match state.result {
46286
+ Some(Ok(job)) => return Ok(job),
46287
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Disconnected)) => {
46288
+ return Err(std::sync::mpsc::RecvError);
46289
+ }
46290
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Timeout)) | None => continue,
46291
+ }
45144
46292
  }
45145
- state.result.unwrap_or_else(|| Err(std::sync::mpsc::RecvError))
45146
46293
  }
45147
46294
  }
45148
46295
 
@@ -45378,7 +46525,7 @@ impl RbTokenizerBackendBridgeRuntimeDispatcher {
45378
46525
  let (sender, receiver) = std::sync::mpsc::sync_channel(Self::QUEUE_CAPACITY);
45379
46526
  let inner = magnus::value::Opaque::from(rb_obj);
45380
46527
  let thread = ruby.thread_create_from_fn(move |ruby| {
45381
- while let Ok(job) = Self::recv_without_gvl(&receiver) {
46528
+ while let Ok(job) = Self::recv_without_gvl(ruby, &receiver) {
45382
46529
  let value = inner.get_inner_with(ruby);
45383
46530
  let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
45384
46531
  job(ruby, value);
@@ -45411,33 +46558,47 @@ impl RbTokenizerBackendBridgeRuntimeDispatcher {
45411
46558
  }
45412
46559
 
45413
46560
  fn recv_without_gvl(
46561
+ ruby: &magnus::Ruby,
45414
46562
  receiver: &std::sync::mpsc::Receiver<RbTokenizerBackendBridgeRuntimeJob>,
45415
46563
  ) -> Result<RbTokenizerBackendBridgeRuntimeJob, std::sync::mpsc::RecvError> {
45416
46564
  struct ReceiveState<'a> {
45417
46565
  receiver: &'a std::sync::mpsc::Receiver<RbTokenizerBackendBridgeRuntimeJob>,
45418
- result: Option<Result<RbTokenizerBackendBridgeRuntimeJob, std::sync::mpsc::RecvError>>,
46566
+ result: Option<Result<RbTokenizerBackendBridgeRuntimeJob, std::sync::mpsc::RecvTimeoutError>>,
45419
46567
  }
45420
46568
 
45421
46569
  unsafe extern "C" fn receive(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
45422
46570
  // SAFETY: the state pointer remains valid until Ruby returns from the
45423
46571
  // without-GVL callback on this same Ruby-created thread.
45424
46572
  let state = unsafe { &mut *(data as *mut ReceiveState<'_>) };
45425
- state.result = Some(state.receiver.recv());
46573
+ state.result = Some(state.receiver.recv_timeout(std::time::Duration::from_millis(50)));
45426
46574
  std::ptr::null_mut()
45427
46575
  }
45428
46576
 
45429
- let mut state = ReceiveState { receiver, result: None };
45430
- // SAFETY: this runs on the dedicated Ruby-created dispatcher thread. The
45431
- // callback touches only Rust channel state and the sender disconnect wakes it.
45432
- unsafe {
45433
- rb_sys::rb_thread_call_without_gvl(
45434
- Some(receive),
45435
- &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
45436
- None,
45437
- std::ptr::null_mut(),
45438
- );
46577
+ loop {
46578
+ let mut state = ReceiveState { receiver, result: None };
46579
+ // SAFETY: the callback only touches Rust channel state, and returns
46580
+ // within 50ms even if a registered sender survives interpreter exit.
46581
+ // The gvl2 API defers interrupts: process them through Magnus below
46582
+ // so a Ruby non-local exit cannot skip Rust stack destructors here.
46583
+ unsafe {
46584
+ rb_sys::rb_thread_call_without_gvl2(
46585
+ Some(receive),
46586
+ &mut state as *mut ReceiveState<'_> as *mut std::ffi::c_void,
46587
+ None,
46588
+ std::ptr::null_mut(),
46589
+ );
46590
+ }
46591
+ if ruby.thread_check_ints().is_err() {
46592
+ return Err(std::sync::mpsc::RecvError);
46593
+ }
46594
+ match state.result {
46595
+ Some(Ok(job)) => return Ok(job),
46596
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Disconnected)) => {
46597
+ return Err(std::sync::mpsc::RecvError);
46598
+ }
46599
+ Some(Err(std::sync::mpsc::RecvTimeoutError::Timeout)) | None => continue,
46600
+ }
45439
46601
  }
45440
- state.result.unwrap_or_else(|| Err(std::sync::mpsc::RecvError))
45441
46602
  }
45442
46603
  }
45443
46604
 
@@ -46792,8 +47953,12 @@ impl From<xberg::candle_ocr::DeepseekOcrBackendOptions> for DeepseekOcrBackendOp
46792
47953
  fn from(val: xberg::candle_ocr::DeepseekOcrBackendOptions) -> Self {
46793
47954
  Self {
46794
47955
  model_path: val.model_path.map(|v| v.to_string()),
47956
+ model_id: val.model_id.map(|v| v.to_string()),
47957
+ hf_revision: val.hf_revision.map(|v| v.to_string()),
47958
+ cache_dir: val.cache_dir.map(|v| v.to_string()),
46795
47959
  device: val.device.map(Into::into),
46796
47960
  version: val.version,
47961
+ dtype: val.dtype.map(Into::into),
46797
47962
  }
46798
47963
  }
46799
47964
  }
@@ -49320,6 +50485,7 @@ impl From<LlmConfig> for xberg::LlmConfig {
49320
50485
  bedrock: val.bedrock.map(Into::into).map(Box::new),
49321
50486
  credential_provider: val.credential_provider.map(Into::into).map(Box::new),
49322
50487
  max_concurrency: val.max_concurrency,
50488
+ max_response_bytes: val.max_response_bytes,
49323
50489
  ..Default::default()
49324
50490
  }
49325
50491
  }
@@ -49358,6 +50524,7 @@ impl From<xberg::LlmConfig> for LlmConfig {
49358
50524
  bedrock: val.bedrock.map(|v| (*v).into()),
49359
50525
  credential_provider: val.credential_provider.map(|v| (*v).into()),
49360
50526
  max_concurrency: val.max_concurrency,
50527
+ max_response_bytes: val.max_response_bytes,
49361
50528
  }
49362
50529
  }
49363
50530
  }
@@ -49998,6 +51165,8 @@ impl From<OcrQualityThresholds> for xberg::OcrQualityThresholds {
49998
51165
  min_undecodable_ratio: val.min_undecodable_ratio,
49999
51166
  enable_provenance_ocr_routing: val.enable_provenance_ocr_routing,
50000
51167
  min_provenance_fallback_ratio: val.min_provenance_fallback_ratio,
51168
+ enable_plausibility_ocr_routing: val.enable_plausibility_ocr_routing,
51169
+ min_reliable_language_chunk_ratio: val.min_reliable_language_chunk_ratio,
50001
51170
  ..Default::default()
50002
51171
  }
50003
51172
  }
@@ -50030,6 +51199,8 @@ impl From<xberg::OcrQualityThresholds> for OcrQualityThresholds {
50030
51199
  min_undecodable_ratio: val.min_undecodable_ratio,
50031
51200
  enable_provenance_ocr_routing: val.enable_provenance_ocr_routing,
50032
51201
  min_provenance_fallback_ratio: val.min_provenance_fallback_ratio,
51202
+ enable_plausibility_ocr_routing: val.enable_plausibility_ocr_routing,
51203
+ min_reliable_language_chunk_ratio: val.min_reliable_language_chunk_ratio,
50033
51204
  }
50034
51205
  }
50035
51206
  }
@@ -50596,6 +51767,8 @@ impl From<PdfMetadata> for xberg::pdf::metadata::PdfMetadata {
50596
51767
  page_count: val.page_count,
50597
51768
  scanned_confidence: val.scanned_confidence,
50598
51769
  scanned_pages: val.scanned_pages.map(|v| v.into_iter().collect()),
51770
+ fabricated_text_pages: val.fabricated_text_pages.map(|v| v.into_iter().collect()),
51771
+ implausible_text_pages: val.implausible_text_pages.map(|v| v.into_iter().collect()),
50599
51772
  layout_gated_pages: val.layout_gated_pages.map(|v| v.into_iter().collect()),
50600
51773
  layout_gate_reasons: val.layout_gate_reasons.map(|v| v.into_iter().collect()),
50601
51774
  ..Default::default()
@@ -50616,6 +51789,8 @@ impl From<xberg::pdf::metadata::PdfMetadata> for PdfMetadata {
50616
51789
  page_count: val.page_count,
50617
51790
  scanned_confidence: val.scanned_confidence,
50618
51791
  scanned_pages: val.scanned_pages.map(|v| v.into_iter().collect()),
51792
+ fabricated_text_pages: val.fabricated_text_pages.map(|v| v.into_iter().collect()),
51793
+ implausible_text_pages: val.implausible_text_pages.map(|v| v.into_iter().collect()),
50619
51794
  layout_gated_pages: val.layout_gated_pages.map(|v| v.into_iter().collect()),
50620
51795
  layout_gate_reasons: val.layout_gate_reasons.map(|v| v.into_iter().collect()),
50621
51796
  }
@@ -52404,6 +53579,18 @@ impl From<xberg::CallMode> for CallMode {
52404
53579
  }
52405
53580
  }
52406
53581
 
53582
+ #[cfg(feature = "candle-ocr")]
53583
+ impl From<xberg::candle_ocr::CandleDeepseekOcrDtype> for CandleDeepseekOcrDtype {
53584
+ fn from(val: xberg::candle_ocr::CandleDeepseekOcrDtype) -> Self {
53585
+ match val {
53586
+ xberg::candle_ocr::CandleDeepseekOcrDtype::Auto => Self::Auto,
53587
+ xberg::candle_ocr::CandleDeepseekOcrDtype::F32 => Self::F32,
53588
+ xberg::candle_ocr::CandleDeepseekOcrDtype::F16 => Self::F16,
53589
+ xberg::candle_ocr::CandleDeepseekOcrDtype::Bf16 => Self::Bf16,
53590
+ }
53591
+ }
53592
+ }
53593
+
52407
53594
  #[cfg(feature = "candle-ocr")]
52408
53595
  impl From<xberg::candle_ocr::CandleDevicePreference> for CandleDevicePreference {
52409
53596
  fn from(val: xberg::candle_ocr::CandleDevicePreference) -> Self {
@@ -52606,6 +53793,16 @@ impl From<xberg::CodeDataNodeKind> for CodeDataNodeKind {
52606
53793
  }
52607
53794
  }
52608
53795
 
53796
+ impl From<ConfidenceSemantics> for xberg::ConfidenceSemantics {
53797
+ fn from(val: ConfidenceSemantics) -> Self {
53798
+ match val {
53799
+ ConfidenceSemantics::Legibility { scale_max } => Self::Legibility { scale_max },
53800
+ ConfidenceSemantics::Uncalibrated => Self::Uncalibrated,
53801
+ ConfidenceSemantics::None => Self::None,
53802
+ }
53803
+ }
53804
+ }
53805
+
52609
53806
  impl From<xberg::ConfidenceSemantics> for ConfidenceSemantics {
52610
53807
  fn from(val: xberg::ConfidenceSemantics) -> Self {
52611
53808
  match val {
@@ -52832,7 +54029,7 @@ impl From<EmbeddingModelType> for xberg::EmbeddingModelType {
52832
54029
  EmbeddingModelType::Preset { name } => Self::Preset { name },
52833
54030
  EmbeddingModelType::Custom { model_id, dimensions } => Self::Custom { model_id, dimensions },
52834
54031
  EmbeddingModelType::Llm { llm } => Self::Llm {
52835
- llm: Box::new(llm.into()),
54032
+ llm: Box::new((*llm).into()),
52836
54033
  },
52837
54034
  EmbeddingModelType::Plugin { name } => Self::Plugin { name },
52838
54035
  }
@@ -52847,7 +54044,9 @@ impl From<xberg::EmbeddingModelType> for EmbeddingModelType {
52847
54044
  model_id: model_id.to_string(),
52848
54045
  dimensions,
52849
54046
  },
52850
- xberg::EmbeddingModelType::Llm { llm } => Self::Llm { llm: (*llm).into() },
54047
+ xberg::EmbeddingModelType::Llm { llm } => Self::Llm {
54048
+ llm: Box::new((*llm).into()),
54049
+ },
52851
54050
  xberg::EmbeddingModelType::Plugin { name } => Self::Plugin { name: name.to_string() },
52852
54051
  }
52853
54052
  }
@@ -52995,7 +54194,7 @@ impl From<FormatMetadata> for xberg::FormatMetadata {
52995
54194
  #[cfg(feature = "pdf")]
52996
54195
  FormatMetadata::Pdf { _0 } => Self::Pdf(_0.into()),
52997
54196
  #[cfg(feature = "office")]
52998
- FormatMetadata::Docx { _0 } => Self::Docx(Box::new(_0.into())),
54197
+ FormatMetadata::Docx { _0 } => Self::Docx(Box::new((*_0).into())),
52999
54198
  FormatMetadata::Excel { _0 } => Self::Excel(_0.into()),
53000
54199
  FormatMetadata::Email { _0 } => Self::Email(_0.into()),
53001
54200
  FormatMetadata::Pptx { _0 } => Self::Pptx(_0.into()),
@@ -53003,7 +54202,7 @@ impl From<FormatMetadata> for xberg::FormatMetadata {
53003
54202
  FormatMetadata::Image { _0 } => Self::Image(_0.into()),
53004
54203
  FormatMetadata::Xml { _0 } => Self::Xml(_0.into()),
53005
54204
  FormatMetadata::Text { _0 } => Self::Text(_0.into()),
53006
- FormatMetadata::Html { _0 } => Self::Html(Box::new(_0.into())),
54205
+ FormatMetadata::Html { _0 } => Self::Html(Box::new((*_0).into())),
53007
54206
  FormatMetadata::Ocr { _0 } => Self::Ocr(_0.into()),
53008
54207
  FormatMetadata::Csv { _0 } => Self::Csv(_0.into()),
53009
54208
  #[cfg(feature = "office")]
@@ -53033,7 +54232,9 @@ impl From<xberg::FormatMetadata> for FormatMetadata {
53033
54232
  #[cfg(feature = "pdf")]
53034
54233
  xberg::FormatMetadata::Pdf(_0) => Self::Pdf { _0: _0.into() },
53035
54234
  #[cfg(feature = "office")]
53036
- xberg::FormatMetadata::Docx(_0) => Self::Docx { _0: (*_0).into() },
54235
+ xberg::FormatMetadata::Docx(_0) => Self::Docx {
54236
+ _0: Box::new((*_0).into()),
54237
+ },
53037
54238
  xberg::FormatMetadata::Excel(_0) => Self::Excel { _0: _0.into() },
53038
54239
  xberg::FormatMetadata::Email(_0) => Self::Email { _0: _0.into() },
53039
54240
  xberg::FormatMetadata::Pptx(_0) => Self::Pptx { _0: _0.into() },
@@ -53041,7 +54242,9 @@ impl From<xberg::FormatMetadata> for FormatMetadata {
53041
54242
  xberg::FormatMetadata::Image(_0) => Self::Image { _0: _0.into() },
53042
54243
  xberg::FormatMetadata::Xml(_0) => Self::Xml { _0: _0.into() },
53043
54244
  xberg::FormatMetadata::Text(_0) => Self::Text { _0: _0.into() },
53044
- xberg::FormatMetadata::Html(_0) => Self::Html { _0: (*_0).into() },
54245
+ xberg::FormatMetadata::Html(_0) => Self::Html {
54246
+ _0: Box::new((*_0).into()),
54247
+ },
53045
54248
  xberg::FormatMetadata::Ocr(_0) => Self::Ocr { _0: _0.into() },
53046
54249
  xberg::FormatMetadata::Csv(_0) => Self::Csv { _0: _0.into() },
53047
54250
  #[cfg(feature = "office")]
@@ -53774,6 +54977,17 @@ impl From<xberg::NodeContent> for NodeContent {
53774
54977
  }
53775
54978
  }
53776
54979
 
54980
+ impl From<OcrBackendType> for xberg::OcrBackendType {
54981
+ fn from(val: OcrBackendType) -> Self {
54982
+ match val {
54983
+ OcrBackendType::Tesseract => Self::Tesseract,
54984
+ OcrBackendType::PaddleOCR => Self::PaddleOCR,
54985
+ OcrBackendType::Candle => Self::Candle,
54986
+ OcrBackendType::Custom => Self::Custom,
54987
+ }
54988
+ }
54989
+ }
54990
+
53777
54991
  impl From<xberg::OcrBackendType> for OcrBackendType {
53778
54992
  fn from(val: xberg::OcrBackendType) -> Self {
53779
54993
  match val {
@@ -53970,6 +55184,16 @@ impl From<xberg::candle_ocr::PaddleOcrVlTaskKind> for PaddleOcrVlTaskKind {
53970
55184
  }
53971
55185
  }
53972
55186
 
55187
+ impl From<PageOrientationHandling> for xberg::PageOrientationHandling {
55188
+ fn from(val: PageOrientationHandling) -> Self {
55189
+ match val {
55190
+ PageOrientationHandling::SelfCorrecting => Self::SelfCorrecting,
55191
+ PageOrientationHandling::RecognisesRotatedText => Self::RecognisesRotatedText,
55192
+ PageOrientationHandling::RequiresUpright => Self::RequiresUpright,
55193
+ }
55194
+ }
55195
+ }
55196
+
53973
55197
  impl From<xberg::PageOrientationHandling> for PageOrientationHandling {
53974
55198
  fn from(val: xberg::PageOrientationHandling) -> Self {
53975
55199
  match val {
@@ -54182,6 +55406,16 @@ impl From<xberg::ProbeStatus> for ProbeStatus {
54182
55406
  }
54183
55407
  }
54184
55408
 
55409
+ impl From<ProcessingStage> for xberg::ProcessingStage {
55410
+ fn from(val: ProcessingStage) -> Self {
55411
+ match val {
55412
+ ProcessingStage::Early => Self::Early,
55413
+ ProcessingStage::Middle => Self::Middle,
55414
+ ProcessingStage::Late => Self::Late,
55415
+ }
55416
+ }
55417
+ }
55418
+
54185
55419
  impl From<xberg::ProcessingStage> for ProcessingStage {
54186
55420
  fn from(val: xberg::ProcessingStage) -> Self {
54187
55421
  match val {
@@ -54315,7 +55549,7 @@ impl From<RerankerModelType> for xberg::RerankerModelType {
54315
55549
  head: head.into(),
54316
55550
  },
54317
55551
  RerankerModelType::Llm { llm } => Self::Llm {
54318
- llm: Box::new(llm.into()),
55552
+ llm: Box::new((*llm).into()),
54319
55553
  },
54320
55554
  RerankerModelType::Plugin { name } => Self::Plugin { name },
54321
55555
  }
@@ -54339,7 +55573,9 @@ impl From<xberg::RerankerModelType> for RerankerModelType {
54339
55573
  max_length,
54340
55574
  head: head.into(),
54341
55575
  },
54342
- xberg::RerankerModelType::Llm { llm } => Self::Llm { llm: (*llm).into() },
55576
+ xberg::RerankerModelType::Llm { llm } => Self::Llm {
55577
+ llm: Box::new((*llm).into()),
55578
+ },
54343
55579
  xberg::RerankerModelType::Plugin { name } => Self::Plugin { name: name.to_string() },
54344
55580
  }
54345
55581
  }
@@ -55576,12 +56812,24 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
55576
56812
  #[cfg(feature = "candle-ocr")]
55577
56813
  class.define_method("model_path", method!(DeepseekOcrBackendOptions::model_path, 0))?;
55578
56814
 
56815
+ #[cfg(feature = "candle-ocr")]
56816
+ class.define_method("model_id", method!(DeepseekOcrBackendOptions::model_id, 0))?;
56817
+
56818
+ #[cfg(feature = "candle-ocr")]
56819
+ class.define_method("hf_revision", method!(DeepseekOcrBackendOptions::hf_revision, 0))?;
56820
+
56821
+ #[cfg(feature = "candle-ocr")]
56822
+ class.define_method("cache_dir", method!(DeepseekOcrBackendOptions::cache_dir, 0))?;
56823
+
55579
56824
  #[cfg(feature = "candle-ocr")]
55580
56825
  class.define_method("device", method!(DeepseekOcrBackendOptions::device, 0))?;
55581
56826
 
55582
56827
  #[cfg(feature = "candle-ocr")]
55583
56828
  class.define_method("version", method!(DeepseekOcrBackendOptions::version, 0))?;
55584
56829
 
56830
+ #[cfg(feature = "candle-ocr")]
56831
+ class.define_method("dtype", method!(DeepseekOcrBackendOptions::dtype, 0))?;
56832
+
55585
56833
  let class = module.define_class("DetectResponse", ruby.class_object())?;
55586
56834
 
55587
56835
  #[cfg(feature = "api")]
@@ -56467,6 +57715,16 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
56467
57715
 
56468
57716
  class.define_method("needs_image_data", method!(ExtractionConfig::needs_image_data, 0))?;
56469
57717
 
57718
+ class.define_method(
57719
+ "runs_ocr_on_embedded_images",
57720
+ method!(ExtractionConfig::runs_ocr_on_embedded_images, 0),
57721
+ )?;
57722
+
57723
+ class.define_method(
57724
+ "wants_own_bytes_in_result",
57725
+ method!(ExtractionConfig::wants_own_bytes_in_result, 0),
57726
+ )?;
57727
+
56470
57728
  class.define_method(
56471
57729
  "needs_image_processing",
56472
57730
  method!(ExtractionConfig::needs_image_processing, 0),
@@ -57468,6 +58726,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
57468
58726
 
57469
58727
  class.define_method("max_concurrency", method!(LlmConfig::max_concurrency, 0))?;
57470
58728
 
58729
+ class.define_method("max_response_bytes", method!(LlmConfig::max_response_bytes, 0))?;
58730
+
57471
58731
  class.define_method("validate", method!(LlmConfig::validate, 0))?;
57472
58732
 
57473
58733
  let class = module.define_class("LlmProviderConfig", ruby.class_object())?;
@@ -57946,6 +59206,16 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
57946
59206
  method!(OcrQualityThresholds::min_provenance_fallback_ratio, 0),
57947
59207
  )?;
57948
59208
 
59209
+ class.define_method(
59210
+ "enable_plausibility_ocr_routing",
59211
+ method!(OcrQualityThresholds::enable_plausibility_ocr_routing, 0),
59212
+ )?;
59213
+
59214
+ class.define_method(
59215
+ "min_reliable_language_chunk_ratio",
59216
+ method!(OcrQualityThresholds::min_reliable_language_chunk_ratio, 0),
59217
+ )?;
59218
+
57949
59219
  let class = module.define_class("OcrRotation", ruby.class_object())?;
57950
59220
 
57951
59221
  class.define_singleton_method("new", function!(OcrRotation::new, -1))?;
@@ -58430,6 +59700,15 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
58430
59700
  #[cfg(feature = "pdf")]
58431
59701
  class.define_method("scanned_pages", method!(PdfMetadata::scanned_pages, 0))?;
58432
59702
 
59703
+ #[cfg(feature = "pdf")]
59704
+ class.define_method("fabricated_text_pages", method!(PdfMetadata::fabricated_text_pages, 0))?;
59705
+
59706
+ #[cfg(feature = "pdf")]
59707
+ class.define_method(
59708
+ "implausible_text_pages",
59709
+ method!(PdfMetadata::implausible_text_pages, 0),
59710
+ )?;
59711
+
58433
59712
  #[cfg(feature = "pdf")]
58434
59713
  class.define_method("layout_gated_pages", method!(PdfMetadata::layout_gated_pages, 0))?;
58435
59714