liter_llm 1.6.4 → 1.7.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e435f10f7f7a4190e12335aed8fee67caeff9da56709ef23b56c20aa77b4c64
4
- data.tar.gz: 674282900ad04eeef03753f41b9c25e1a8ebe7676881fc0ae9a101f4aaa2be30
3
+ metadata.gz: 336f60aa43a86dbbddd88a10084f24180cc2e02dda02294754e66203b7d2698d
4
+ data.tar.gz: '0816afd2387ade4af23e63e05f24d970da571a7f799f41399548e49ea42fa885'
5
5
  SHA512:
6
- metadata.gz: acf1c4dff2e26e01aa11f1c1c2bba1b66d170cebf8ef11dafe69bf5b4b6c8b7cbe7083a451df2b215221fd539c8cdbc2b0fce65067516930c7f23a21848d0262
7
- data.tar.gz: a2451cff7040de44bb9ff738811d103b3b54eaf397da918e4bde59cc044ec0cdcf6624d0f38bb2165963bae3dd9dc0e21fb46ee89311c4506094a7b39c90f0f6
6
+ metadata.gz: 777e16f5ebecd0e90576f0714fc1b7b8f6bf41e8b7ccb6f23734d581bd2909a0c99c2279a9edcd45c56f742116915c7b226cbfdda0054980cab2fbb3842c1f6a
7
+ data.tar.gz: 2008f0e64537acc3fa8578667dff3cc10c5fd45a008d40785dc74aa526113a285523e7eddc8b0a1c0839b7deceb904f7379d6a6accea4cdc1cb9ebdbe629b676
@@ -1875,9 +1875,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
1875
1875
 
1876
1876
  [[package]]
1877
1877
  name = "liter-llm"
1878
- version = "1.6.4"
1878
+ version = "1.7.1"
1879
1879
  source = "registry+https://github.com/rust-lang/crates.io-index"
1880
- checksum = "82177d82ab01638c1567473be387c83492e6201bc59b399c12a254bde133d4ee"
1880
+ checksum = "0552ad2a855bd57c422933227a1d664ad66b1e773dd2244a14194500880ef544"
1881
1881
  dependencies = [
1882
1882
  "ahash 0.8.12",
1883
1883
  "async-trait",
@@ -1917,7 +1917,7 @@ dependencies = [
1917
1917
 
1918
1918
  [[package]]
1919
1919
  name = "liter-llm-rb"
1920
- version = "1.6.4"
1920
+ version = "1.7.1"
1921
1921
  dependencies = [
1922
1922
  "futures",
1923
1923
  "liter-llm",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "liter-llm-rb"
3
- version = "1.6.4"
3
+ version = "1.7.1"
4
4
  edition = "2024"
5
5
  license = "MIT"
6
6
  description = "Universal LLM API client with Rust-powered polyglot bindings."
@@ -24,7 +24,7 @@ wasm-http = ["liter-llm/wasm-http"]
24
24
 
25
25
  [dependencies]
26
26
  futures = "0.3"
27
- liter-llm = { version = "1.6.4", features = ["native-http", "full"] }
27
+ liter-llm = { version = "1.7.1", features = ["native-http", "full"] }
28
28
  magnus = "0.8"
29
29
  rb-sys = ">=0.9, <0.9.128"
30
30
  serde = { version = "1", features = ["derive"] }
@@ -1,5 +1,5 @@
1
1
  // This file is auto-generated by alef. DO NOT EDIT.
2
- // alef:hash:52effca2513e1c5f8a9a5b90a372d645388de4b92e4f822acc38d97502c322b6
2
+ // alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
3
3
  // Re-generate with: alef generate
4
4
  #![allow(dead_code, unused_imports, unused_variables)]
5
5
  #![allow(
@@ -67,7 +67,7 @@ fn json_to_ruby(handle: &Ruby, val: serde_json::Value) -> magnus::Value {
67
67
  #[serde(default)]
68
68
  #[magnus::wrap(class = "LiterLlm::SystemMessage")]
69
69
  pub struct SystemMessage {
70
- content: String,
70
+ content: UserContent,
71
71
  name: Option<String>,
72
72
  }
73
73
 
@@ -101,7 +101,10 @@ unsafe impl TryConvertOwned for SystemMessage {}
101
101
 
102
102
  impl Default for SystemMessage {
103
103
  fn default() -> Self {
104
- liter_llm::types::SystemMessage::default().into()
104
+ Self {
105
+ content: Default::default(),
106
+ name: None,
107
+ }
105
108
  }
106
109
  }
107
110
 
@@ -114,7 +117,7 @@ impl SystemMessage {
114
117
  Ok(Self {
115
118
  content: kwargs
116
119
  .get(ruby.to_symbol("content"))
117
- .and_then(|v| String::try_convert(v).ok())
120
+ .and_then(|v| UserContent::try_convert(v).ok())
118
121
  .unwrap_or_default(),
119
122
  name: kwargs
120
123
  .get(ruby.to_symbol("name"))
@@ -122,18 +125,13 @@ impl SystemMessage {
122
125
  })
123
126
  }
124
127
 
125
- fn content(&self) -> String {
128
+ fn content(&self) -> UserContent {
126
129
  self.content.clone()
127
130
  }
128
131
 
129
132
  fn name(&self) -> Option<String> {
130
133
  self.name.clone()
131
134
  }
132
-
133
- #[allow(clippy::should_implement_trait)]
134
- fn to_s(&self) -> String {
135
- self.content.clone()
136
- }
137
135
  }
138
136
 
139
137
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -420,7 +418,7 @@ impl AudioContent {
420
418
  #[serde(default)]
421
419
  #[magnus::wrap(class = "LiterLlm::AssistantMessage")]
422
420
  pub struct AssistantMessage {
423
- content: Option<String>,
421
+ content: Option<AssistantContent>,
424
422
  name: Option<String>,
425
423
  tool_calls: Option<Vec<ToolCall>>,
426
424
  refusal: Option<String>,
@@ -476,7 +474,7 @@ impl AssistantMessage {
476
474
  Ok(Self {
477
475
  content: kwargs
478
476
  .get(ruby.to_symbol("content"))
479
- .and_then(|v| String::try_convert(v).ok()),
477
+ .and_then(|v| AssistantContent::try_convert(v).ok()),
480
478
  name: kwargs
481
479
  .get(ruby.to_symbol("name"))
482
480
  .and_then(|v| String::try_convert(v).ok()),
@@ -492,7 +490,7 @@ impl AssistantMessage {
492
490
  })
493
491
  }
494
492
 
495
- fn content(&self) -> Option<String> {
493
+ fn content(&self) -> Option<AssistantContent> {
496
494
  self.content.clone()
497
495
  }
498
496
 
@@ -512,9 +510,64 @@ impl AssistantMessage {
512
510
  self.function_call.clone()
513
511
  }
514
512
 
515
- #[allow(clippy::should_implement_trait)]
516
- fn to_s(&self) -> String {
517
- self.content.clone().unwrap_or_default()
513
+ fn text(&self) -> Option<String> {
514
+ let core_self = liter_llm::types::AssistantMessage {
515
+ content: self.content.clone().map(Into::into),
516
+
517
+ name: self.name.clone(),
518
+
519
+ tool_calls: self.tool_calls.clone().map(|v| v.into_iter().map(Into::into).collect()),
520
+
521
+ refusal: self.refusal.clone(),
522
+
523
+ function_call: self.function_call.clone().map(Into::into),
524
+ };
525
+ core_self.text()
526
+ }
527
+
528
+ fn refusal_text(&self) -> Option<String> {
529
+ let core_self = liter_llm::types::AssistantMessage {
530
+ content: self.content.clone().map(Into::into),
531
+
532
+ name: self.name.clone(),
533
+
534
+ tool_calls: self.tool_calls.clone().map(|v| v.into_iter().map(Into::into).collect()),
535
+
536
+ refusal: self.refusal.clone(),
537
+
538
+ function_call: self.function_call.clone().map(Into::into),
539
+ };
540
+ core_self.refusal_text().map(|v| v.to_owned())
541
+ }
542
+
543
+ fn output_images(&self) -> Vec<ImageUrl> {
544
+ let core_self = liter_llm::types::AssistantMessage {
545
+ content: self.content.clone().map(Into::into),
546
+
547
+ name: self.name.clone(),
548
+
549
+ tool_calls: self.tool_calls.clone().map(|v| v.into_iter().map(Into::into).collect()),
550
+
551
+ refusal: self.refusal.clone(),
552
+
553
+ function_call: self.function_call.clone().map(Into::into),
554
+ };
555
+ core_self.output_images().into_iter().map(Into::into).collect()
556
+ }
557
+
558
+ fn output_audio(&self) -> Vec<AudioContent> {
559
+ let core_self = liter_llm::types::AssistantMessage {
560
+ content: self.content.clone().map(Into::into),
561
+
562
+ name: self.name.clone(),
563
+
564
+ tool_calls: self.tool_calls.clone().map(|v| v.into_iter().map(Into::into).collect()),
565
+
566
+ refusal: self.refusal.clone(),
567
+
568
+ function_call: self.function_call.clone().map(Into::into),
569
+ };
570
+ core_self.output_audio().into_iter().map(Into::into).collect()
518
571
  }
519
572
  }
520
573
 
@@ -1445,6 +1498,7 @@ pub struct ChatCompletionRequest {
1445
1498
  stream_options: Option<StreamOptions>,
1446
1499
  seed: Option<i64>,
1447
1500
  reasoning_effort: Option<ReasoningEffort>,
1501
+ modalities: Option<Vec<Modality>>,
1448
1502
  extra_body: Option<String>,
1449
1503
  }
1450
1504
 
@@ -1498,6 +1552,7 @@ impl Default for ChatCompletionRequest {
1498
1552
  stream_options: None,
1499
1553
  seed: None,
1500
1554
  reasoning_effort: None,
1555
+ modalities: None,
1501
1556
  extra_body: None,
1502
1557
  }
1503
1558
  }
@@ -1567,6 +1622,9 @@ impl ChatCompletionRequest {
1567
1622
  reasoning_effort: kwargs
1568
1623
  .get(ruby.to_symbol("reasoning_effort"))
1569
1624
  .and_then(|v| ReasoningEffort::try_convert(v).ok()),
1625
+ modalities: kwargs
1626
+ .get(ruby.to_symbol("modalities"))
1627
+ .and_then(|v| <Vec<Modality>>::try_convert(v).ok()),
1570
1628
  extra_body: kwargs
1571
1629
  .get(ruby.to_symbol("extra_body"))
1572
1630
  .and_then(|v| String::try_convert(v).ok()),
@@ -1649,6 +1707,10 @@ impl ChatCompletionRequest {
1649
1707
  self.reasoning_effort.clone()
1650
1708
  }
1651
1709
 
1710
+ fn modalities(&self) -> Option<Vec<Modality>> {
1711
+ self.modalities.clone()
1712
+ }
1713
+
1652
1714
  fn extra_body(&self) -> Option<String> {
1653
1715
  self.extra_body.clone()
1654
1716
  }
@@ -2885,6 +2947,75 @@ impl Image {
2885
2947
  }
2886
2948
  }
2887
2949
 
2950
+ #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
2951
+ #[serde(default)]
2952
+ #[magnus::wrap(class = "LiterLlm::DecodedDataUrl")]
2953
+ pub struct DecodedDataUrl {
2954
+ mime: String,
2955
+ data: Vec<u8>,
2956
+ }
2957
+
2958
+ unsafe impl IntoValueFromNative for DecodedDataUrl {}
2959
+
2960
+ impl magnus::TryConvert for DecodedDataUrl {
2961
+ fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
2962
+ if let Ok(r) = <&DecodedDataUrl as magnus::TryConvert>::try_convert(val) {
2963
+ return Ok(r.clone());
2964
+ }
2965
+ let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
2966
+ s
2967
+ } else {
2968
+ val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
2969
+ magnus::Error::new(
2970
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2971
+ format!("no implicit conversion into DecodedDataUrl: {}", e),
2972
+ )
2973
+ })?
2974
+ };
2975
+ serde_json::from_str::<DecodedDataUrl>(&json_str).map_err(|e| {
2976
+ magnus::Error::new(
2977
+ unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
2978
+ format!("failed to deserialize DecodedDataUrl: {}", e),
2979
+ )
2980
+ })
2981
+ }
2982
+ }
2983
+
2984
+ unsafe impl TryConvertOwned for DecodedDataUrl {}
2985
+
2986
+ impl Default for DecodedDataUrl {
2987
+ fn default() -> Self {
2988
+ liter_llm::image::DecodedDataUrl::default().into()
2989
+ }
2990
+ }
2991
+
2992
+ impl DecodedDataUrl {
2993
+ fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
2994
+ let ruby = unsafe { magnus::Ruby::get_unchecked() };
2995
+ let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
2996
+ let (kwargs_opt,) = args.optional;
2997
+ let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
2998
+ Ok(Self {
2999
+ mime: kwargs
3000
+ .get(ruby.to_symbol("mime"))
3001
+ .and_then(|v| String::try_convert(v).ok())
3002
+ .unwrap_or_default(),
3003
+ data: kwargs
3004
+ .get(ruby.to_symbol("data"))
3005
+ .and_then(|v| <Vec<u8>>::try_convert(v).ok())
3006
+ .unwrap_or_default(),
3007
+ })
3008
+ }
3009
+
3010
+ fn mime(&self) -> String {
3011
+ self.mime.clone()
3012
+ }
3013
+
3014
+ fn data(&self) -> Vec<u8> {
3015
+ self.data.clone()
3016
+ }
3017
+ }
3018
+
2888
3019
  #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
2889
3020
  #[serde(default)]
2890
3021
  #[magnus::wrap(class = "LiterLlm::CreateSpeechRequest")]
@@ -7710,6 +7841,122 @@ impl magnus::TryConvert for ImageDetail {
7710
7841
  unsafe impl IntoValueFromNative for ImageDetail {}
7711
7842
  unsafe impl TryConvertOwned for ImageDetail {}
7712
7843
 
7844
+ #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
7845
+ #[serde(untagged)]
7846
+ pub enum AssistantContent {
7847
+ Text(String),
7848
+ Parts(Vec<AssistantPart>),
7849
+ }
7850
+
7851
+ impl Default for AssistantContent {
7852
+ fn default() -> Self {
7853
+ Self::Text(Default::default())
7854
+ }
7855
+ }
7856
+
7857
+ impl magnus::IntoValue for AssistantContent {
7858
+ fn into_value_with(self, handle: &Ruby) -> magnus::Value {
7859
+ match serde_json::to_value(&self) {
7860
+ Ok(v) => json_to_ruby(handle, v),
7861
+ Err(_) => handle.qnil().into_value_with(handle),
7862
+ }
7863
+ }
7864
+ }
7865
+
7866
+ impl magnus::TryConvert for AssistantContent {
7867
+ fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
7868
+ // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
7869
+ // For unit enums or when passed as a string, fall back to string-based conversion.
7870
+ let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
7871
+ s
7872
+ } else {
7873
+ val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
7874
+ magnus::Error::new(
7875
+ unsafe { Ruby::get_unchecked() }.exception_type_error(),
7876
+ format!("no implicit conversion into AssistantContent: {}", e),
7877
+ )
7878
+ })?
7879
+ };
7880
+ // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
7881
+ // If that fails, try treating it as a plain string value and wrap in quotes
7882
+ // If both fail, try as Custom variant (for untagged enum support)
7883
+ serde_json::from_str(&json_str)
7884
+ .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
7885
+ .or_else(|_| {
7886
+ // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
7887
+ match serde_json::to_value(&json_str) {
7888
+ Ok(val) => serde_json::from_value(val),
7889
+ Err(e) => Err(e),
7890
+ }
7891
+ })
7892
+ .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
7893
+ }
7894
+ }
7895
+
7896
+ unsafe impl IntoValueFromNative for AssistantContent {}
7897
+ unsafe impl TryConvertOwned for AssistantContent {}
7898
+
7899
+ #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
7900
+ #[serde(tag = "type")]
7901
+ #[serde(rename_all = "snake_case")]
7902
+ pub enum AssistantPart {
7903
+ Text { text: String },
7904
+ Refusal { refusal: String },
7905
+ OutputImage { image_url: ImageUrl },
7906
+ OutputAudio { audio: AudioContent },
7907
+ }
7908
+
7909
+ impl Default for AssistantPart {
7910
+ fn default() -> Self {
7911
+ Self::Text {
7912
+ text: Default::default(),
7913
+ }
7914
+ }
7915
+ }
7916
+
7917
+ impl magnus::IntoValue for AssistantPart {
7918
+ fn into_value_with(self, handle: &Ruby) -> magnus::Value {
7919
+ match serde_json::to_value(&self) {
7920
+ Ok(v) => json_to_ruby(handle, v),
7921
+ Err(_) => handle.qnil().into_value_with(handle),
7922
+ }
7923
+ }
7924
+ }
7925
+
7926
+ impl magnus::TryConvert for AssistantPart {
7927
+ fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
7928
+ // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
7929
+ // For unit enums or when passed as a string, fall back to string-based conversion.
7930
+ let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
7931
+ s
7932
+ } else {
7933
+ val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
7934
+ magnus::Error::new(
7935
+ unsafe { Ruby::get_unchecked() }.exception_type_error(),
7936
+ format!("no implicit conversion into AssistantPart: {}", e),
7937
+ )
7938
+ })?
7939
+ };
7940
+ // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
7941
+ // If that fails, try treating it as a plain string value and wrap in quotes
7942
+ // If both fail, try as Custom variant (for untagged enum support)
7943
+ serde_json::from_str(&json_str)
7944
+ .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
7945
+ .or_else(|_| {
7946
+ // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
7947
+ match serde_json::to_value(&json_str) {
7948
+ Ok(val) => serde_json::from_value(val),
7949
+ Err(e) => Err(e),
7950
+ }
7951
+ })
7952
+ .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
7953
+ }
7954
+ }
7955
+
7956
+ unsafe impl IntoValueFromNative for AssistantPart {}
7957
+ unsafe impl TryConvertOwned for AssistantPart {}
7958
+ impl AssistantPart {}
7959
+
7713
7960
  #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7714
7961
  pub enum ToolType {
7715
7962
  #[serde(rename = "function")]
@@ -7964,6 +8211,51 @@ impl magnus::TryConvert for StopSequence {
7964
8211
  unsafe impl IntoValueFromNative for StopSequence {}
7965
8212
  unsafe impl TryConvertOwned for StopSequence {}
7966
8213
 
8214
+ #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
8215
+ #[serde(rename_all = "snake_case")]
8216
+ pub enum Modality {
8217
+ Text,
8218
+ Audio,
8219
+ Image,
8220
+ }
8221
+
8222
+ impl Default for Modality {
8223
+ fn default() -> Self {
8224
+ Self::Text
8225
+ }
8226
+ }
8227
+
8228
+ impl magnus::IntoValue for Modality {
8229
+ fn into_value_with(self, handle: &Ruby) -> magnus::Value {
8230
+ let sym = match self {
8231
+ Modality::Text => "text",
8232
+ Modality::Audio => "audio",
8233
+ Modality::Image => "image",
8234
+ };
8235
+ handle.to_symbol(sym).into_value_with(handle)
8236
+ }
8237
+ }
8238
+
8239
+ impl magnus::TryConvert for Modality {
8240
+ fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
8241
+ let s: String = magnus::TryConvert::try_convert(val)?;
8242
+ // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
8243
+ // and a lowercase fallback so fixtures written in any of those styles work.
8244
+ match s.as_str() {
8245
+ "text" | "Text" => Ok(Modality::Text),
8246
+ "audio" | "Audio" => Ok(Modality::Audio),
8247
+ "image" | "Image" => Ok(Modality::Image),
8248
+ other => Err(magnus::Error::new(
8249
+ unsafe { Ruby::get_unchecked() }.exception_arg_error(),
8250
+ format!("invalid Modality value: {other}"),
8251
+ )),
8252
+ }
8253
+ }
8254
+ }
8255
+
8256
+ unsafe impl IntoValueFromNative for Modality {}
8257
+ unsafe impl TryConvertOwned for Modality {}
8258
+
7967
8259
  #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
7968
8260
  #[serde(rename_all = "snake_case")]
7969
8261
  pub enum FinishReason {
@@ -8762,6 +9054,27 @@ fn create_client_from_json(json: String) -> Result<DefaultClient, Error> {
8762
9054
  })
8763
9055
  }
8764
9056
 
9057
+ fn encode_data_url(args: &[magnus::Value]) -> Result<String, Error> {
9058
+ let args = magnus::scan_args::scan_args::<(Vec<u8>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
9059
+ let (bytes,) = args.required;
9060
+
9061
+ let (mime,) = args.optional;
9062
+
9063
+ let mime: Option<String> = mime.and_then(|v| {
9064
+ if v.is_nil() {
9065
+ None
9066
+ } else {
9067
+ Some(String::try_convert(v).unwrap_or_default())
9068
+ }
9069
+ });
9070
+
9071
+ Ok(liter_llm::image::encode_data_url(&bytes, mime.as_deref()))
9072
+ }
9073
+
9074
+ fn decode_data_url(url: String) -> Option<DecodedDataUrl> {
9075
+ liter_llm::image::decode_data_url(&url).map(Into::into)
9076
+ }
9077
+
8765
9078
  fn register_custom_provider(config: magnus::Value) -> Result<(), Error> {
8766
9079
  let config: liter_llm::CustomProviderConfig = {
8767
9080
  let binding_val: CustomProviderConfig = CustomProviderConfig::try_convert(config)
@@ -8880,6 +9193,11 @@ fn ensure_crypto_provider() -> () {
8880
9193
  liter_llm::ensure_crypto_provider()
8881
9194
  }
8882
9195
 
9196
+ #[cfg(all(feature = "native-http", target_os = "windows"))]
9197
+ fn ensure_crypto_provider() -> () {
9198
+ liter_llm::ensure_crypto_provider()
9199
+ }
9200
+
8883
9201
  fn chat_stream(engine: DefaultClient, req: ChatCompletionRequest) -> Result<magnus::Value, Error> {
8884
9202
  engine.chat_stream(req)
8885
9203
  }
@@ -8888,7 +9206,7 @@ fn chat_stream(engine: DefaultClient, req: ChatCompletionRequest) -> Result<magn
8888
9206
  impl From<SystemMessage> for liter_llm::types::SystemMessage {
8889
9207
  fn from(val: SystemMessage) -> Self {
8890
9208
  Self {
8891
- content: val.content,
9209
+ content: val.content.into(),
8892
9210
  name: val.name,
8893
9211
  }
8894
9212
  }
@@ -8898,7 +9216,7 @@ impl From<SystemMessage> for liter_llm::types::SystemMessage {
8898
9216
  impl From<liter_llm::types::SystemMessage> for SystemMessage {
8899
9217
  fn from(val: liter_llm::types::SystemMessage) -> Self {
8900
9218
  Self {
8901
- content: val.content.to_string(),
9219
+ content: val.content.into(),
8902
9220
  name: val.name.map(|v| v.to_string()),
8903
9221
  }
8904
9222
  }
@@ -8988,7 +9306,7 @@ impl From<liter_llm::types::AudioContent> for AudioContent {
8988
9306
  impl From<AssistantMessage> for liter_llm::types::AssistantMessage {
8989
9307
  fn from(val: AssistantMessage) -> Self {
8990
9308
  Self {
8991
- content: val.content,
9309
+ content: val.content.map(Into::into),
8992
9310
  name: val.name,
8993
9311
  tool_calls: val.tool_calls.map(|v| v.into_iter().map(Into::into).collect()),
8994
9312
  refusal: val.refusal,
@@ -9001,7 +9319,7 @@ impl From<AssistantMessage> for liter_llm::types::AssistantMessage {
9001
9319
  impl From<liter_llm::types::AssistantMessage> for AssistantMessage {
9002
9320
  fn from(val: liter_llm::types::AssistantMessage) -> Self {
9003
9321
  Self {
9004
- content: val.content.map(|v| v.to_string()),
9322
+ content: val.content.map(Into::into),
9005
9323
  name: val.name.map(|v| v.to_string()),
9006
9324
  tool_calls: val.tool_calls.map(|v| v.into_iter().map(Into::into).collect()),
9007
9325
  refusal: val.refusal.map(|v| v.to_string()),
@@ -9285,6 +9603,7 @@ impl From<ChatCompletionRequest> for liter_llm::types::ChatCompletionRequest {
9285
9603
  stream_options: val.stream_options.map(Into::into),
9286
9604
  seed: val.seed,
9287
9605
  reasoning_effort: val.reasoning_effort.map(Into::into),
9606
+ modalities: val.modalities.map(|v| v.into_iter().map(Into::into).collect()),
9288
9607
  extra_body: val.extra_body.as_ref().and_then(|s| serde_json::from_str(s).ok()),
9289
9608
  }
9290
9609
  }
@@ -9313,6 +9632,7 @@ impl From<liter_llm::types::ChatCompletionRequest> for ChatCompletionRequest {
9313
9632
  stream_options: val.stream_options.map(Into::into),
9314
9633
  seed: val.seed,
9315
9634
  reasoning_effort: val.reasoning_effort.map(Into::into),
9635
+ modalities: val.modalities.map(|v| v.into_iter().map(Into::into).collect()),
9316
9636
  extra_body: val.extra_body.as_ref().map(ToString::to_string),
9317
9637
  }
9318
9638
  }
@@ -9598,6 +9918,26 @@ impl From<liter_llm::types::Image> for Image {
9598
9918
  }
9599
9919
  }
9600
9920
 
9921
+ #[allow(clippy::redundant_closure, clippy::useless_conversion)]
9922
+ impl From<DecodedDataUrl> for liter_llm::image::DecodedDataUrl {
9923
+ fn from(val: DecodedDataUrl) -> Self {
9924
+ Self {
9925
+ mime: val.mime,
9926
+ data: val.data.to_vec().into(),
9927
+ }
9928
+ }
9929
+ }
9930
+
9931
+ #[allow(clippy::redundant_closure, clippy::useless_conversion)]
9932
+ impl From<liter_llm::image::DecodedDataUrl> for DecodedDataUrl {
9933
+ fn from(val: liter_llm::image::DecodedDataUrl) -> Self {
9934
+ Self {
9935
+ mime: val.mime.to_string(),
9936
+ data: val.data.to_vec().into(),
9937
+ }
9938
+ }
9939
+ }
9940
+
9601
9941
  #[allow(clippy::redundant_closure, clippy::useless_conversion)]
9602
9942
  impl From<CreateSpeechRequest> for liter_llm::types::CreateSpeechRequest {
9603
9943
  fn from(val: CreateSpeechRequest) -> Self {
@@ -10813,6 +11153,52 @@ impl From<liter_llm::types::ImageDetail> for ImageDetail {
10813
11153
  }
10814
11154
  }
10815
11155
 
11156
+ impl From<AssistantContent> for liter_llm::types::AssistantContent {
11157
+ fn from(val: AssistantContent) -> Self {
11158
+ match val {
11159
+ AssistantContent::Text(_0) => Self::Text(_0),
11160
+ AssistantContent::Parts(_0) => Self::Parts(_0.into_iter().map(Into::into).collect()),
11161
+ }
11162
+ }
11163
+ }
11164
+
11165
+ impl From<liter_llm::types::AssistantContent> for AssistantContent {
11166
+ fn from(val: liter_llm::types::AssistantContent) -> Self {
11167
+ match val {
11168
+ liter_llm::types::AssistantContent::Text(_0) => Self::Text(_0),
11169
+ liter_llm::types::AssistantContent::Parts(_0) => Self::Parts(_0.into_iter().map(Into::into).collect()),
11170
+ }
11171
+ }
11172
+ }
11173
+
11174
+ impl From<AssistantPart> for liter_llm::types::AssistantPart {
11175
+ fn from(val: AssistantPart) -> Self {
11176
+ match val {
11177
+ AssistantPart::Text { text } => Self::Text { text },
11178
+ AssistantPart::Refusal { refusal } => Self::Refusal { refusal },
11179
+ AssistantPart::OutputImage { image_url } => Self::OutputImage {
11180
+ image_url: image_url.into(),
11181
+ },
11182
+ AssistantPart::OutputAudio { audio } => Self::OutputAudio { audio: audio.into() },
11183
+ }
11184
+ }
11185
+ }
11186
+
11187
+ impl From<liter_llm::types::AssistantPart> for AssistantPart {
11188
+ fn from(val: liter_llm::types::AssistantPart) -> Self {
11189
+ match val {
11190
+ liter_llm::types::AssistantPart::Text { text } => Self::Text { text: text.to_string() },
11191
+ liter_llm::types::AssistantPart::Refusal { refusal } => Self::Refusal {
11192
+ refusal: refusal.to_string(),
11193
+ },
11194
+ liter_llm::types::AssistantPart::OutputImage { image_url } => Self::OutputImage {
11195
+ image_url: image_url.into(),
11196
+ },
11197
+ liter_llm::types::AssistantPart::OutputAudio { audio } => Self::OutputAudio { audio: audio.into() },
11198
+ }
11199
+ }
11200
+ }
11201
+
10816
11202
  impl From<ToolType> for liter_llm::types::ToolType {
10817
11203
  fn from(val: ToolType) -> Self {
10818
11204
  match val {
@@ -10909,6 +11295,26 @@ impl From<liter_llm::types::StopSequence> for StopSequence {
10909
11295
  }
10910
11296
  }
10911
11297
 
11298
+ impl From<Modality> for liter_llm::types::Modality {
11299
+ fn from(val: Modality) -> Self {
11300
+ match val {
11301
+ Modality::Text => Self::Text,
11302
+ Modality::Audio => Self::Audio,
11303
+ Modality::Image => Self::Image,
11304
+ }
11305
+ }
11306
+ }
11307
+
11308
+ impl From<liter_llm::types::Modality> for Modality {
11309
+ fn from(val: liter_llm::types::Modality) -> Self {
11310
+ match val {
11311
+ liter_llm::types::Modality::Text => Self::Text,
11312
+ liter_llm::types::Modality::Audio => Self::Audio,
11313
+ liter_llm::types::Modality::Image => Self::Image,
11314
+ }
11315
+ }
11316
+ }
11317
+
10912
11318
  impl From<FinishReason> for liter_llm::types::FinishReason {
10913
11319
  fn from(val: FinishReason) -> Self {
10914
11320
  match val {
@@ -11328,8 +11734,6 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
11328
11734
 
11329
11735
  class.define_method("name", method!(SystemMessage::name, 0))?;
11330
11736
 
11331
- class.define_method("to_s", method!(SystemMessage::to_s, 0))?;
11332
-
11333
11737
  let class = module.define_class("UserMessage", ruby.class_object())?;
11334
11738
 
11335
11739
  class.define_singleton_method("new", function!(UserMessage::new, -1))?;
@@ -11376,7 +11780,13 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
11376
11780
 
11377
11781
  class.define_method("function_call", method!(AssistantMessage::function_call, 0))?;
11378
11782
 
11379
- class.define_method("to_s", method!(AssistantMessage::to_s, 0))?;
11783
+ class.define_method("text", method!(AssistantMessage::text, 0))?;
11784
+
11785
+ class.define_method("refusal_text", method!(AssistantMessage::refusal_text, 0))?;
11786
+
11787
+ class.define_method("output_images", method!(AssistantMessage::output_images, 0))?;
11788
+
11789
+ class.define_method("output_audio", method!(AssistantMessage::output_audio, 0))?;
11380
11790
 
11381
11791
  let class = module.define_class("ToolMessage", ruby.class_object())?;
11382
11792
 
@@ -11542,6 +11952,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
11542
11952
 
11543
11953
  class.define_method("reasoning_effort", method!(ChatCompletionRequest::reasoning_effort, 0))?;
11544
11954
 
11955
+ class.define_method("modalities", method!(ChatCompletionRequest::modalities, 0))?;
11956
+
11545
11957
  class.define_method("extra_body", method!(ChatCompletionRequest::extra_body, 0))?;
11546
11958
 
11547
11959
  let class = module.define_class("StreamOptions", ruby.class_object())?;
@@ -11726,6 +12138,14 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
11726
12138
 
11727
12139
  class.define_method("revised_prompt", method!(Image::revised_prompt, 0))?;
11728
12140
 
12141
+ let class = module.define_class("DecodedDataUrl", ruby.class_object())?;
12142
+
12143
+ class.define_singleton_method("new", function!(DecodedDataUrl::new, -1))?;
12144
+
12145
+ class.define_method("mime", method!(DecodedDataUrl::mime, 0))?;
12146
+
12147
+ class.define_method("data", method!(DecodedDataUrl::data, 0))?;
12148
+
11729
12149
  let class = module.define_class("CreateSpeechRequest", ruby.class_object())?;
11730
12150
 
11731
12151
  class.define_singleton_method("new", function!(CreateSpeechRequest::new, -1))?;
@@ -12405,6 +12825,10 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
12405
12825
 
12406
12826
  module.define_module_function("create_client_from_json", function!(create_client_from_json, 1))?;
12407
12827
 
12828
+ module.define_module_function("encode_data_url", function!(encode_data_url, -1))?;
12829
+
12830
+ module.define_module_function("decode_data_url", function!(decode_data_url, 1))?;
12831
+
12408
12832
  module.define_module_function("register_custom_provider", function!(register_custom_provider, 1))?;
12409
12833
 
12410
12834
  module.define_module_function("unregister_custom_provider", function!(unregister_custom_provider, 1))?;
@@ -12429,6 +12853,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
12429
12853
 
12430
12854
  module.define_module_function("ensure_crypto_provider", function!(ensure_crypto_provider, 0))?;
12431
12855
 
12856
+ module.define_module_function("ensure_crypto_provider", function!(ensure_crypto_provider, 0))?;
12857
+
12432
12858
  module.define_module_function("chat_stream", function!(chat_stream, 2))?;
12433
12859
 
12434
12860
  let liter_llm_error_info_class = module.define_class("LiterLlmErrorInfo", ruby.class_object())?;
@@ -1,5 +1,5 @@
1
1
  # This file is auto-generated by alef — DO NOT EDIT.
2
- # alef:hash:52effca2513e1c5f8a9a5b90a372d645388de4b92e4f822acc38d97502c322b6
2
+ # alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
3
3
  # To regenerate: alef generate
4
4
  # To verify freshness: alef verify --exit-code
5
5
  # frozen_string_literal: true
@@ -314,7 +314,142 @@ module LiterLlm
314
314
  end
315
315
 
316
316
  module LiterLlm
317
- # Response format constraint.
317
+ # One part of a structured assistant response.
318
+ #
319
+ # `#[serde(tag = "type", rename_all = "snake_case")]` matches OpenAI's
320
+ # parts-spec discriminator (`"type": "text"`, `"type": "output_image"`, …).
321
+ module AssistantPart
322
+ extend T::Helpers
323
+ extend T::Sig
324
+
325
+ interface!
326
+
327
+ # Dispatch from a Hash to the appropriate variant constructor.
328
+ # @param hash [Hash] with discriminator field and variant-specific fields
329
+ # @return [variant_class] an instance of the appropriate variant
330
+ sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.untyped) }
331
+ def self.from_hash(hash)
332
+ discriminator = hash[:type] || hash["type"]
333
+ case discriminator
334
+ when "text" then AssistantPartText.from_hash(hash)
335
+ when "refusal" then AssistantPartRefusal.from_hash(hash)
336
+ when "output_image" then AssistantPartOutputImage.from_hash(hash)
337
+ when "output_audio" then AssistantPartOutputAudio.from_hash(hash)
338
+ else raise "Unknown discriminator: #{discriminator}"
339
+ end
340
+ end
341
+ end
342
+ ## A text segment of the response.
343
+ AssistantPartText = Data.define(:text) do
344
+ include AssistantPart
345
+ extend T::Sig
346
+
347
+ # The text content of this part.
348
+ sig { returns(String) }
349
+ def text = super # rubocop:disable Lint/UselessMethodDefinition
350
+ sig { returns(T::Boolean) }
351
+ def text? = true
352
+ sig { returns(T::Boolean) }
353
+ def refusal? = false
354
+ sig { returns(T::Boolean) }
355
+ def output_image? = false
356
+ sig { returns(T::Boolean) }
357
+ def output_audio? = false
358
+ # @param hash [Hash] deserialized from the native extension
359
+ # @return [self]
360
+ sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
361
+ def self.from_hash(hash)
362
+ new(text: hash[:text] || hash["text"])
363
+ end
364
+ end
365
+ ## A refusal — the model declined to respond.
366
+ AssistantPartRefusal = Data.define(:refusal) do
367
+ include AssistantPart
368
+ extend T::Sig
369
+
370
+ # The refusal reason.
371
+ sig { returns(String) }
372
+ def refusal = super # rubocop:disable Lint/UselessMethodDefinition
373
+ sig { returns(T::Boolean) }
374
+ def text? = false
375
+ sig { returns(T::Boolean) }
376
+ def refusal? = true
377
+ sig { returns(T::Boolean) }
378
+ def output_image? = false
379
+ sig { returns(T::Boolean) }
380
+ def output_audio? = false
381
+ # @param hash [Hash] deserialized from the native extension
382
+ # @return [self]
383
+ sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
384
+ def self.from_hash(hash)
385
+ new(refusal: hash[:refusal] || hash["refusal"])
386
+ end
387
+ end
388
+ ## An image produced by the model (e.g. `gpt-image-1`, Gemini Imagen).
389
+ AssistantPartOutputImage = Data.define(:image_url) do
390
+ include AssistantPart
391
+ extend T::Sig
392
+
393
+ # Image URL or data URI referencing the generated image.
394
+ sig { returns(ImageUrl) }
395
+ def image_url = super # rubocop:disable Lint/UselessMethodDefinition
396
+ sig { returns(T::Boolean) }
397
+ def text? = false
398
+ sig { returns(T::Boolean) }
399
+ def refusal? = false
400
+ sig { returns(T::Boolean) }
401
+ def output_image? = true
402
+ sig { returns(T::Boolean) }
403
+ def output_audio? = false
404
+ # @param hash [Hash] deserialized from the native extension
405
+ # @return [self]
406
+ sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
407
+ def self.from_hash(hash)
408
+ new(image_url: hash[:image_url] || hash["image_url"])
409
+ end
410
+ end
411
+ ## Audio produced by the model (e.g. `gpt-4o-audio-preview`).
412
+ AssistantPartOutputAudio = Data.define(:audio) do
413
+ include AssistantPart
414
+ extend T::Sig
415
+
416
+ # Base64-encoded audio data and its format.
417
+ sig { returns(AudioContent) }
418
+ def audio = super # rubocop:disable Lint/UselessMethodDefinition
419
+ sig { returns(T::Boolean) }
420
+ def text? = false
421
+ sig { returns(T::Boolean) }
422
+ def refusal? = false
423
+ sig { returns(T::Boolean) }
424
+ def output_image? = false
425
+ sig { returns(T::Boolean) }
426
+ def output_audio? = true
427
+ # @param hash [Hash] deserialized from the native extension
428
+ # @return [self]
429
+ sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
430
+ def self.from_hash(hash)
431
+ new(audio: hash[:audio] || hash["audio"])
432
+ end
433
+ end
434
+ end
435
+
436
+ module LiterLlm
437
+ # Wire format for the chat completions `response_format` field.
438
+ #
439
+ # # Provider mapping
440
+ #
441
+ # - **OpenAI** (and OpenAI-compatible providers): emitted verbatim as
442
+ # `{"type": "json_schema", "json_schema": {...}}` per the
443
+ # chat-completions spec.
444
+ # - **Gemini / Vertex AI**: translated to
445
+ # `generationConfig.responseMimeType = "application/json"` and
446
+ # `generationConfig.responseSchema = <schema>`. The `name`,
447
+ # `description`, and `strict` fields are dropped — Gemini's
448
+ # structured-output API does not consume them.
449
+ # - **Anthropic**: no native JSON mode. A system instruction is
450
+ # prepended asking the model to respond with valid JSON.
451
+ # `strict` is advisory only; callers should still validate the
452
+ # returned JSON if the schema is load-bearing.
318
453
  module ResponseFormat
319
454
  extend T::Helpers
320
455
  extend T::Sig
@@ -1,10 +1,10 @@
1
1
  # This file is auto-generated by alef — DO NOT EDIT.
2
- # alef:hash:52effca2513e1c5f8a9a5b90a372d645388de4b92e4f822acc38d97502c322b6
2
+ # alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
3
3
  # To regenerate: alef generate
4
4
  # To verify freshness: alef verify --exit-code
5
5
  # frozen_string_literal: true
6
6
 
7
7
  module LiterLlm
8
8
  ## The version string for this package.
9
- VERSION = "1.6.4"
9
+ VERSION = "1.7.1"
10
10
  end
data/lib/liter_llm.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # This file is auto-generated by alef — DO NOT EDIT.
2
- # alef:hash:52effca2513e1c5f8a9a5b90a372d645388de4b92e4f822acc38d97502c322b6
2
+ # alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
3
3
  # To regenerate: alef generate
4
4
  # To verify freshness: alef verify --exit-code
5
5
  # frozen_string_literal: true
data/sig/types.rbs CHANGED
@@ -1,5 +1,5 @@
1
1
  # This file is auto-generated by alef — DO NOT EDIT.
2
- # alef:hash:52effca2513e1c5f8a9a5b90a372d645388de4b92e4f822acc38d97502c322b6
2
+ # alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
3
3
  # To regenerate: alef generate
4
4
  # To verify freshness: alef verify --exit-code
5
5
 
@@ -10,10 +10,10 @@ module LiterLlm
10
10
  type json_value = Hash[String, untyped] | Array[untyped] | String | Integer | Float | bool | nil
11
11
 
12
12
  class SystemMessage
13
- attr_accessor content: String?
13
+ attr_accessor content: UserContent?
14
14
  attr_accessor name: String?
15
15
 
16
- def initialize: (?content: String, ?name: String) -> void
16
+ def initialize: (?content: UserContent, ?name: String) -> void
17
17
  end
18
18
 
19
19
  class UserMessage
@@ -45,13 +45,17 @@ module LiterLlm
45
45
  end
46
46
 
47
47
  class AssistantMessage
48
- attr_accessor content: String?
48
+ attr_accessor content: AssistantContent?
49
49
  attr_accessor name: String?
50
50
  attr_accessor tool_calls: Array[ToolCall]?
51
51
  attr_accessor refusal: String?
52
52
  attr_accessor function_call: FunctionCall?
53
53
 
54
- def initialize: (?content: String, ?name: String, ?tool_calls: Array[ToolCall], ?refusal: String, ?function_call: FunctionCall) -> void
54
+ def initialize: (?content: AssistantContent, ?name: String, ?tool_calls: Array[ToolCall], ?refusal: String, ?function_call: FunctionCall) -> void
55
+ def text: () -> String?
56
+ def refusal_text: () -> String?
57
+ def output_images: () -> Array[ImageUrl]
58
+ def output_audio: () -> Array[AudioContent]
55
59
  end
56
60
 
57
61
  class ToolMessage
@@ -165,9 +169,10 @@ module LiterLlm
165
169
  attr_accessor stream_options: StreamOptions?
166
170
  attr_accessor seed: Integer?
167
171
  attr_accessor reasoning_effort: ReasoningEffort?
172
+ attr_accessor modalities: Array[Modality]?
168
173
  attr_accessor extra_body: json_value?
169
174
 
170
- def initialize: (?model: String, ?messages: Array[Message], ?temperature: Float, ?top_p: Float, ?n: Integer, ?stream: bool, ?stop: StopSequence, ?max_tokens: Integer, ?presence_penalty: Float, ?frequency_penalty: Float, ?logit_bias: Hash[String, Float], ?user: String, ?tools: Array[ChatCompletionTool], ?tool_choice: ToolChoice, ?parallel_tool_calls: bool, ?response_format: ResponseFormat, ?stream_options: StreamOptions, ?seed: Integer, ?reasoning_effort: ReasoningEffort, ?extra_body: json_value) -> void
175
+ def initialize: (?model: String, ?messages: Array[Message], ?temperature: Float, ?top_p: Float, ?n: Integer, ?stream: bool, ?stop: StopSequence, ?max_tokens: Integer, ?presence_penalty: Float, ?frequency_penalty: Float, ?logit_bias: Hash[String, Float], ?user: String, ?tools: Array[ChatCompletionTool], ?tool_choice: ToolChoice, ?parallel_tool_calls: bool, ?response_format: ResponseFormat, ?stream_options: StreamOptions, ?seed: Integer, ?reasoning_effort: ReasoningEffort, ?modalities: Array[Modality], ?extra_body: json_value) -> void
171
176
  end
172
177
 
173
178
  class StreamOptions
@@ -299,6 +304,13 @@ module LiterLlm
299
304
  def initialize: (?url: String, ?b64_json: String, ?revised_prompt: String) -> void
300
305
  end
301
306
 
307
+ class DecodedDataUrl
308
+ attr_accessor mime: String?
309
+ attr_accessor data: String?
310
+
311
+ def initialize: (?mime: String, ?data: String) -> void
312
+ end
313
+
302
314
  class CreateSpeechRequest
303
315
  attr_accessor model: String?
304
316
  attr_accessor input: String?
@@ -780,6 +792,12 @@ module LiterLlm
780
792
  type value = :low | :high | :auto
781
793
  end
782
794
 
795
+ class AssistantContent
796
+ end
797
+
798
+ class AssistantPart
799
+ end
800
+
783
801
  class ToolType
784
802
  type value = :function
785
803
  end
@@ -797,6 +815,10 @@ module LiterLlm
797
815
  class StopSequence
798
816
  end
799
817
 
818
+ class Modality
819
+ type value = :text | :audio | :image
820
+ end
821
+
800
822
  class FinishReason
801
823
  type value = :stop | :length | :tool_calls | :content_filter | :function_call | :other
802
824
  end
@@ -859,6 +881,10 @@ module LiterLlm
859
881
 
860
882
  def self.create_client_from_json: (String json) -> DefaultClient
861
883
 
884
+ def self.encode_data_url: (String bytes, ?String mime) -> String
885
+
886
+ def self.decode_data_url: (String url) -> DecodedDataUrl?
887
+
862
888
  def self.register_custom_provider: (CustomProviderConfig config) -> void
863
889
 
864
890
  def self.unregister_custom_provider: (String name) -> bool
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liter_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.4
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Na'aman Hirschfeld
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-17 00:00:00.000000000 Z
11
+ date: 2026-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys