sorbet-baml 0.1.0 → 0.3.0

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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +94 -0
  3. data/README.md +315 -122
  4. data/Rakefile +2 -2
  5. data/docs-site/.gitignore +48 -0
  6. data/docs-site/Gemfile +5 -0
  7. data/docs-site/Gemfile.lock +140 -0
  8. data/docs-site/Rakefile +3 -0
  9. data/docs-site/bridgetown.config.yml +15 -0
  10. data/docs-site/config/initializers.rb +9 -0
  11. data/docs-site/config/puma.rb +9 -0
  12. data/docs-site/config.ru +5 -0
  13. data/docs-site/esbuild.config.js +11 -0
  14. data/docs-site/frontend/javascript/index.js +22 -0
  15. data/docs-site/frontend/styles/index.css +61 -0
  16. data/docs-site/package.json +18 -0
  17. data/docs-site/postcss.config.js +6 -0
  18. data/docs-site/server/roda_app.rb +9 -0
  19. data/docs-site/src/_components/head.liquid +26 -0
  20. data/docs-site/src/_components/nav.liquid +68 -0
  21. data/docs-site/src/_layouts/default.liquid +27 -0
  22. data/docs-site/src/_layouts/doc.liquid +39 -0
  23. data/docs-site/src/advanced-usage.md +598 -0
  24. data/docs-site/src/getting-started.md +170 -0
  25. data/docs-site/src/index.md +183 -0
  26. data/docs-site/src/troubleshooting.md +317 -0
  27. data/docs-site/src/type-mapping.md +236 -0
  28. data/docs-site/tailwind.config.js +85 -0
  29. data/examples/description_parameters.rb +49 -0
  30. data/lib/sorbet_baml/comment_extractor.rb +51 -54
  31. data/lib/sorbet_baml/converter.rb +69 -35
  32. data/lib/sorbet_baml/dependency_resolver.rb +11 -11
  33. data/lib/sorbet_baml/description_extension.rb +34 -0
  34. data/lib/sorbet_baml/description_extractor.rb +34 -0
  35. data/lib/sorbet_baml/dspy_tool_converter.rb +97 -0
  36. data/lib/sorbet_baml/dspy_tool_extensions.rb +23 -0
  37. data/lib/sorbet_baml/enum_extensions.rb +2 -2
  38. data/lib/sorbet_baml/struct_extensions.rb +2 -2
  39. data/lib/sorbet_baml/tool_extensions.rb +23 -0
  40. data/lib/sorbet_baml/type_mapper.rb +35 -37
  41. data/lib/sorbet_baml/version.rb +1 -1
  42. data/lib/sorbet_baml.rb +41 -10
  43. data/sorbet/config +2 -0
  44. data/sorbet/rbi/gems/anthropic@1.5.0.rbi +21252 -0
  45. data/sorbet/rbi/gems/async@2.27.3.rbi +9 -0
  46. data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +9 -0
  47. data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +424 -0
  48. data/sorbet/rbi/gems/connection_pool@2.5.3.rbi +9 -0
  49. data/sorbet/rbi/gems/console@1.33.0.rbi +9 -0
  50. data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +672 -0
  51. data/sorbet/rbi/gems/dry-core@1.1.0.rbi +1729 -0
  52. data/sorbet/rbi/gems/dry-logger@1.1.0.rbi +1317 -0
  53. data/sorbet/rbi/gems/dspy@0.19.1.rbi +6677 -0
  54. data/sorbet/rbi/gems/ffi@1.17.2.rbi +2174 -0
  55. data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +9 -0
  56. data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +9 -0
  57. data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +9 -0
  58. data/sorbet/rbi/gems/google-protobuf@4.32.0.rbi +9 -0
  59. data/sorbet/rbi/gems/googleapis-common-protos-types@1.20.0.rbi +9 -0
  60. data/sorbet/rbi/gems/informers@1.2.1.rbi +1875 -0
  61. data/sorbet/rbi/gems/io-event@1.12.1.rbi +9 -0
  62. data/sorbet/rbi/gems/metrics@0.13.0.rbi +9 -0
  63. data/sorbet/rbi/gems/onnxruntime@0.10.0.rbi +304 -0
  64. data/sorbet/rbi/gems/openai@0.16.0.rbi +68055 -0
  65. data/sorbet/rbi/gems/opentelemetry-api@1.6.0.rbi +9 -0
  66. data/sorbet/rbi/gems/opentelemetry-common@0.22.0.rbi +9 -0
  67. data/sorbet/rbi/gems/opentelemetry-exporter-otlp@0.30.0.rbi +9 -0
  68. data/sorbet/rbi/gems/opentelemetry-registry@0.4.0.rbi +9 -0
  69. data/sorbet/rbi/gems/opentelemetry-sdk@1.8.1.rbi +9 -0
  70. data/sorbet/rbi/gems/opentelemetry-semantic_conventions@1.11.0.rbi +9 -0
  71. data/sorbet/rbi/gems/polars-df@0.20.0.rbi +9 -0
  72. data/sorbet/rbi/gems/sorbet-result@1.4.0.rbi +242 -0
  73. data/sorbet/rbi/gems/sorbet-schema@0.9.2.rbi +743 -0
  74. data/sorbet/rbi/gems/sorbet-struct-comparable@1.3.0.rbi +48 -0
  75. data/sorbet/rbi/gems/tokenizers@0.5.5.rbi +754 -0
  76. data/sorbet/rbi/gems/traces@0.17.0.rbi +9 -0
  77. data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +1429 -0
  78. metadata +67 -7
  79. data/docs/README.md +0 -117
  80. data/docs/advanced-usage.md +0 -427
  81. data/docs/getting-started.md +0 -91
  82. data/docs/troubleshooting.md +0 -291
  83. data/docs/type-mapping.md +0 -192
@@ -1,9 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
- require "set"
6
- require_relative "comment_extractor"
4
+ require 'sorbet-runtime'
5
+ require_relative 'comment_extractor'
7
6
 
8
7
  module SorbetBaml
9
8
  # Main converter class for transforming Sorbet types to BAML
@@ -18,24 +17,24 @@ module SorbetBaml
18
17
  sig { params(klasses: T::Array[T.class_of(T::Struct)], options: T::Hash[Symbol, T.untyped]).returns(String) }
19
18
  def self.from_structs(klasses, options = {})
20
19
  converter = new(options)
21
-
20
+
22
21
  if converter.instance_variable_get(:@include_dependencies)
23
22
  # When dependencies are enabled, collect all unique dependencies and convert once
24
23
  all_dependencies = Set.new
25
24
  enum_dependencies = Set.new
26
-
25
+
27
26
  klasses.each do |klass|
28
27
  deps = DependencyResolver.resolve_dependencies(klass)
29
28
  all_dependencies.merge(deps)
30
29
  enum_deps = converter.send(:find_enum_dependencies, deps)
31
30
  enum_dependencies.merge(enum_deps)
32
31
  end
33
-
32
+
34
33
  # Convert all unique types
35
34
  converted_types = []
36
35
  enum_dependencies.each { |enum_klass| converted_types << converter.convert_enum(enum_klass) }
37
36
  all_dependencies.each { |struct_klass| converted_types << converter.send(:convert_single_struct, struct_klass) }
38
-
37
+
39
38
  converted_types.join("\n\n")
40
39
  else
41
40
  # When dependencies are disabled, convert each struct individually
@@ -43,6 +42,11 @@ module SorbetBaml
43
42
  end
44
43
  end
45
44
 
45
+ sig { params(klass: T.class_of(T::Struct), options: T::Hash[Symbol, T.untyped]).returns(String) }
46
+ def self.from_tool(klass, options = {})
47
+ new(options).convert_tool(klass)
48
+ end
49
+
46
50
  sig { params(klass: T.class_of(T::Enum), options: T::Hash[Symbol, T.untyped]).returns(String) }
47
51
  def self.from_enum(klass, options = {})
48
52
  new(options).convert_enum(klass)
@@ -61,15 +65,15 @@ module SorbetBaml
61
65
  if @include_dependencies
62
66
  # Get all dependencies in correct order and convert them all
63
67
  dependencies = DependencyResolver.resolve_dependencies(klass)
64
-
68
+
65
69
  # Also find all enum dependencies
66
70
  enum_dependencies = find_enum_dependencies(dependencies)
67
-
71
+
68
72
  # Convert enums first, then structs
69
73
  converted_types = []
70
74
  enum_dependencies.each { |enum_klass| converted_types << convert_enum(enum_klass) }
71
75
  dependencies.each { |dep_klass| converted_types << convert_single_struct(dep_klass) }
72
-
76
+
73
77
  converted_types.join("\n\n")
74
78
  else
75
79
  # Just convert the single struct
@@ -82,17 +86,17 @@ module SorbetBaml
82
86
  class_name = klass.name || klass.to_s
83
87
  simple_name = class_name.split('::').last
84
88
  lines = ["enum #{simple_name} {"]
85
-
89
+
86
90
  # Extract comments if requested
87
91
  comments = @include_descriptions ? CommentExtractor.extract_enum_comments(klass) : {}
88
-
92
+
89
93
  # Get all enum values by calling values method
90
94
  enum_values = klass.values
91
95
  enum_values.each do |enum_instance|
92
96
  value = enum_instance.serialize
93
-
97
+
94
98
  # Find the constant name for this value
95
- constant_name = nil
99
+ constant_name = T.let(nil, T.nilable(String))
96
100
  klass.constants.each do |const_name|
97
101
  const_value = klass.const_get(const_name)
98
102
  if const_value.is_a?(klass) && const_value.serialize == value
@@ -100,18 +104,48 @@ module SorbetBaml
100
104
  break
101
105
  end
102
106
  end
103
-
107
+
104
108
  line = "#{' ' * @indent_size}\"#{value}\""
105
-
109
+
106
110
  # Add description if available (BAML uses @description annotations, not comments)
107
111
  if @include_descriptions && constant_name && comments[constant_name]
108
- line += " @description(\"#{comments[constant_name].gsub('"', '\\"')}\")"
112
+ line += " @description(\"#{T.must(comments[constant_name]).gsub('"', '\\"')}\")"
113
+ end
114
+
115
+ lines << line
116
+ end
117
+
118
+ lines << '}'
119
+ lines.join("\n")
120
+ end
121
+
122
+ sig { params(klass: T.class_of(T::Struct)).returns(String) }
123
+ def convert_tool(klass)
124
+ # Tools generate BAML class definitions similar to structs but for tool specifications
125
+ # They can include action fields with @description annotations
126
+ props = klass.props
127
+
128
+ class_name = klass.name || klass.to_s
129
+ simple_name = class_name.split('::').last
130
+ lines = ["class #{simple_name} {"]
131
+
132
+ # Extract comments if requested
133
+ comments = @include_descriptions ? CommentExtractor.extract_field_comments(klass) : {}
134
+
135
+ props.each do |name, prop_info|
136
+ baml_type = TypeMapper.map_type(prop_info[:type_object])
137
+ line = "#{' ' * @indent_size}#{name} #{baml_type}"
138
+
139
+ # Add description if available (BAML uses @description annotations)
140
+ if @include_descriptions && comments[name.to_s]
141
+ escaped_comment = T.must(comments[name.to_s]).gsub('"', '\\"')
142
+ line += " @description(\"#{escaped_comment}\")"
109
143
  end
110
-
144
+
111
145
  lines << line
112
146
  end
113
-
114
- lines << "}"
147
+
148
+ lines << '}'
115
149
  lines.join("\n")
116
150
  end
117
151
 
@@ -120,49 +154,49 @@ module SorbetBaml
120
154
  sig { params(klass: T.class_of(T::Struct)).returns(String) }
121
155
  def convert_single_struct(klass)
122
156
  props = klass.props
123
-
157
+
124
158
  class_name = klass.name || klass.to_s
125
159
  simple_name = class_name.split('::').last
126
160
  lines = ["class #{simple_name} {"]
127
-
161
+
128
162
  # Extract comments if requested
129
163
  comments = @include_descriptions ? CommentExtractor.extract_field_comments(klass) : {}
130
-
164
+
131
165
  props.each do |name, prop_info|
132
166
  baml_type = TypeMapper.map_type(prop_info[:type_object])
133
167
  line = "#{' ' * @indent_size}#{name} #{baml_type}"
134
-
168
+
135
169
  # Add description if available (BAML uses @description annotations)
136
170
  if @include_descriptions && comments[name.to_s]
137
- escaped_comment = comments[name.to_s].gsub('"', '\\"')
171
+ escaped_comment = T.must(comments[name.to_s]).gsub('"', '\\"')
138
172
  line += " @description(\"#{escaped_comment}\")"
139
173
  end
140
-
174
+
141
175
  lines << line
142
176
  end
143
-
144
- lines << "}"
177
+
178
+ lines << '}'
145
179
  lines.join("\n")
146
180
  end
147
-
181
+
148
182
  sig { params(struct_classes: T::Array[T.class_of(T::Struct)]).returns(T::Array[T.class_of(T::Enum)]) }
149
183
  def find_enum_dependencies(struct_classes)
150
184
  enum_deps = Set.new
151
-
185
+
152
186
  struct_classes.each do |struct_klass|
153
187
  struct_klass.props.each do |_name, prop_info|
154
188
  type_object = prop_info[:type_object]
155
189
  enum_deps.merge(extract_enum_types(type_object))
156
190
  end
157
191
  end
158
-
192
+
159
193
  enum_deps.to_a
160
194
  end
161
-
195
+
162
196
  sig { params(type_object: T.untyped).returns(T::Array[T.class_of(T::Enum)]) }
163
197
  def extract_enum_types(type_object)
164
198
  return [] if type_object.nil?
165
-
199
+
166
200
  case type_object
167
201
  when T::Types::Simple
168
202
  extract_enum_from_simple_type(type_object.raw_type)
@@ -182,7 +216,7 @@ module SorbetBaml
182
216
  end
183
217
  end
184
218
  end
185
-
219
+
186
220
  sig { params(raw_type: T.untyped).returns(T::Array[T.class_of(T::Enum)]) }
187
221
  def extract_enum_from_simple_type(raw_type)
188
222
  # Check if this raw_type is a T::Enum subclass
@@ -193,4 +227,4 @@ module SorbetBaml
193
227
  end
194
228
  end
195
229
  end
196
- end
230
+ end
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
4
+ require 'sorbet-runtime'
5
5
 
6
6
  module SorbetBaml
7
7
  # Resolves dependencies between T::Struct types and orders them topologically
@@ -23,10 +23,10 @@ module SorbetBaml
23
23
  def resolve_dependencies(klass)
24
24
  @visited.clear
25
25
  @dependencies.clear
26
-
26
+
27
27
  # Perform depth-first search to find all dependencies
28
28
  visit(klass)
29
-
29
+
30
30
  # Dependencies are already in correct topological order
31
31
  # (dependencies first, then the types that depend on them)
32
32
  @dependencies
@@ -37,15 +37,15 @@ module SorbetBaml
37
37
  sig { params(klass: T.class_of(T::Struct)).void }
38
38
  def visit(klass)
39
39
  return if @visited.include?(klass)
40
-
40
+
41
41
  @visited.add(klass)
42
-
42
+
43
43
  # Find all T::Struct dependencies in this class
44
44
  struct_dependencies = find_struct_dependencies(klass)
45
-
45
+
46
46
  # Visit dependencies first (depth-first)
47
47
  struct_dependencies.each { |dep| visit(dep) }
48
-
48
+
49
49
  # Add this class after its dependencies
50
50
  @dependencies << klass
51
51
  end
@@ -53,19 +53,19 @@ module SorbetBaml
53
53
  sig { params(klass: T.class_of(T::Struct)).returns(T::Array[T.class_of(T::Struct)]) }
54
54
  def find_struct_dependencies(klass)
55
55
  dependencies = []
56
-
56
+
57
57
  klass.props.each do |_name, prop_info|
58
58
  type_object = prop_info[:type_object]
59
59
  dependencies.concat(extract_struct_types(type_object))
60
60
  end
61
-
61
+
62
62
  dependencies.uniq
63
63
  end
64
64
 
65
65
  sig { params(type_object: T.untyped).returns(T::Array[T.class_of(T::Struct)]) }
66
66
  def extract_struct_types(type_object)
67
67
  return [] if type_object.nil?
68
-
68
+
69
69
  case type_object
70
70
  when T::Types::Simple
71
71
  extract_from_simple_type(type_object.raw_type)
@@ -96,4 +96,4 @@ module SorbetBaml
96
96
  end
97
97
  end
98
98
  end
99
- end
99
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module SorbetBaml
7
+ # Extension module to add description: parameter support to T::Struct
8
+ module DescriptionExtension
9
+ extend T::Sig
10
+
11
+ # Override const to support description parameter
12
+ sig { params(name: Symbol, type: T.untyped, description: T.nilable(String), kwargs: T.untyped).void }
13
+ def const(name, type, description: nil, **kwargs)
14
+ if description
15
+ super(name, type, extra: { description: description }, **kwargs)
16
+ else
17
+ super(name, type, **kwargs)
18
+ end
19
+ end
20
+
21
+ # Override prop to support description parameter
22
+ sig { params(name: Symbol, type: T.untyped, description: T.nilable(String), kwargs: T.untyped).void }
23
+ def prop(name, type, description: nil, **kwargs)
24
+ if description
25
+ super(name, type, extra: { description: description }, **kwargs)
26
+ else
27
+ super(name, type, **kwargs)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ # Automatically extend T::Struct with description support
34
+ T::Struct.extend(SorbetBaml::DescriptionExtension)
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module SorbetBaml
7
+ # Extracts description parameters from T::Struct prop and const declarations
8
+ class DescriptionExtractor
9
+ extend T::Sig
10
+
11
+ sig { params(klass: T::Class[T.anything]).returns(T::Hash[String, T.nilable(String)]) }
12
+ def self.extract_prop_descriptions(klass)
13
+ descriptions = {}
14
+
15
+ # Check if this is a T::Struct with props
16
+ return descriptions unless klass.respond_to?(:props)
17
+
18
+ begin
19
+ T.unsafe(klass).props.each do |field_name, prop_info|
20
+ next unless prop_info.is_a?(Hash)
21
+
22
+ # Check if the prop has a description in the :extra field
23
+ extra = prop_info[:extra]
24
+ descriptions[field_name.to_s] = extra[:description] if extra.is_a?(Hash) && extra[:description].is_a?(String)
25
+ end
26
+ rescue StandardError
27
+ # Handle any errors gracefully and return empty hash
28
+ return {}
29
+ end
30
+
31
+ descriptions
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,97 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module SorbetBaml
7
+ # Converter for DSPy tools to BAML format
8
+ class DSPyToolConverter
9
+ extend T::Sig
10
+
11
+ sig { params(klass: T.class_of(DSPy::Tools::Base), options: T::Hash[Symbol, T.untyped]).returns(String) }
12
+ def self.from_dspy_tool(klass, options = {})
13
+ new(options).convert_dspy_tool(klass)
14
+ end
15
+
16
+ sig { params(options: T::Hash[Symbol, T.untyped]).void }
17
+ def initialize(options = {})
18
+ @options = options
19
+ @indent_size = T.let(options.fetch(:indent_size, 2), Integer)
20
+ @include_descriptions = T.let(options.fetch(:include_descriptions, true), T::Boolean)
21
+ end
22
+
23
+ sig { params(klass: T.class_of(DSPy::Tools::Base)).returns(String) }
24
+ def convert_dspy_tool(klass)
25
+ # Extract tool metadata from DSPy tool
26
+ tool_name = klass.tool_name_value || klass.name&.split('::')&.last
27
+ tool_description = klass.tool_description_value
28
+
29
+ # Get parameters from DSPy's call_schema which extracts from Sorbet signatures
30
+ call_schema = klass.call_schema
31
+ parameters = call_schema[:properties] || {}
32
+ required_params = call_schema[:required] || []
33
+
34
+ lines = []
35
+
36
+ # Add tool description as class-level comment if available
37
+ lines << "// #{tool_description}" if @include_descriptions && tool_description
38
+
39
+ # Generate BAML class definition
40
+ lines << "class #{tool_name} {"
41
+
42
+ parameters.each do |param_name, param_info|
43
+ # Convert JSON schema type to BAML type
44
+ baml_type = json_schema_type_to_baml(param_info)
45
+
46
+ # Add optional marker if parameter is not required
47
+ baml_type += '?' unless required_params.include?(param_name.to_s)
48
+
49
+ line = "#{' ' * @indent_size}#{param_name} #{baml_type}"
50
+
51
+ # Add description from schema if available
52
+ if @include_descriptions && param_info[:description]
53
+ escaped_description = param_info[:description].gsub('"', '\\"')
54
+ line += " @description(\"#{escaped_description}\")"
55
+ end
56
+
57
+ lines << line
58
+ end
59
+
60
+ lines << '}'
61
+ lines.join("\n")
62
+ end
63
+
64
+ private
65
+
66
+ sig { params(json_schema_info: T::Hash[Symbol, T.untyped]).returns(String) }
67
+ def json_schema_type_to_baml(json_schema_info)
68
+ type = json_schema_info[:type]
69
+
70
+ case type
71
+ when :string, 'string'
72
+ 'string'
73
+ when :integer, 'integer'
74
+ 'int'
75
+ when :number, 'number'
76
+ 'float'
77
+ when :boolean, 'boolean'
78
+ 'bool'
79
+ when :array, 'array'
80
+ item_type = json_schema_info.dig(:items, :type)
81
+ case item_type
82
+ when :string, 'string' then 'string[]'
83
+ when :integer, 'integer' then 'int[]'
84
+ when :number, 'number' then 'float[]'
85
+ when :boolean, 'boolean' then 'bool[]'
86
+ else 'string[]' # fallback
87
+ end
88
+ when :object, 'object'
89
+ # For object types, we'll default to a map<string, string>
90
+ # In a more sophisticated implementation, we'd handle nested objects
91
+ 'map<string, string>'
92
+ else
93
+ 'string' # fallback for unknown types
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module SorbetBaml
7
+ # Extensions to add BAML conversion methods to DSPy::Tools::Base
8
+ module DSPyToolExtensions
9
+ extend T::Sig
10
+
11
+ # Convert this DSPy tool to BAML tool definition
12
+ sig { params(options: T::Hash[Symbol, T.untyped]).returns(String) }
13
+ def to_baml(options = {})
14
+ baml_tool_definition(options)
15
+ end
16
+
17
+ # Convert this DSPy tool to BAML tool definition with options
18
+ sig { params(options: T::Hash[Symbol, T.untyped]).returns(String) }
19
+ def baml_tool_definition(options = {})
20
+ SorbetBaml::DSPyToolConverter.from_dspy_tool(T.cast(self, T.class_of(DSPy::Tools::Base)), options)
21
+ end
22
+ end
23
+ end
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
4
+ require 'sorbet-runtime'
5
5
 
6
6
  module SorbetBaml
7
7
  # Extensions to add BAML conversion methods to T::Enum
@@ -20,4 +20,4 @@ module SorbetBaml
20
20
  SorbetBaml::Converter.from_enum(T.cast(self, T.class_of(T::Enum)), options)
21
21
  end
22
22
  end
23
- end
23
+ end
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
4
+ require 'sorbet-runtime'
5
5
 
6
6
  module SorbetBaml
7
7
  # Extensions to add BAML conversion methods to T::Struct
@@ -20,4 +20,4 @@ module SorbetBaml
20
20
  SorbetBaml::Converter.from_struct(T.cast(self, T.class_of(T::Struct)), options)
21
21
  end
22
22
  end
23
- end
23
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module SorbetBaml
7
+ # Extensions to add BAML tool conversion methods to T::Struct
8
+ module ToolExtensions
9
+ extend T::Sig
10
+
11
+ # Convert this struct to BAML tool definition
12
+ sig { params(options: T::Hash[Symbol, T.untyped]).returns(String) }
13
+ def to_baml_tool(options = {})
14
+ baml_tool_definition(options)
15
+ end
16
+
17
+ # Convert this struct to BAML tool definition with options
18
+ sig { params(options: T::Hash[Symbol, T.untyped]).returns(String) }
19
+ def baml_tool_definition(options = {})
20
+ SorbetBaml::Converter.from_tool(T.cast(self, T.class_of(T::Struct)), options)
21
+ end
22
+ end
23
+ end
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
4
+ require 'sorbet-runtime'
5
5
 
6
6
  module SorbetBaml
7
7
  # Maps Sorbet type objects to BAML type strings
@@ -10,8 +10,8 @@ module SorbetBaml
10
10
 
11
11
  sig { params(type_object: T.untyped).returns(String) }
12
12
  def self.map_type(type_object)
13
- return "string" if type_object.nil?
14
-
13
+ return 'string' if type_object.nil?
14
+
15
15
  case type_object
16
16
  when T::Types::Simple
17
17
  map_simple_type(type_object.raw_type)
@@ -25,7 +25,7 @@ module SorbetBaml
25
25
  map_union_type(type_object)
26
26
  else
27
27
  # Fallback for unknown types
28
- "unknown"
28
+ 'unknown'
29
29
  end
30
30
  end
31
31
  end
@@ -33,27 +33,27 @@ module SorbetBaml
33
33
  sig { params(raw_type: T.untyped).returns(String) }
34
34
  def self.map_simple_type(raw_type)
35
35
  case raw_type.name
36
- when "String"
37
- "string"
38
- when "Integer"
39
- "int"
40
- when "Float"
41
- "float"
42
- when "TrueClass", "FalseClass"
43
- "bool"
44
- when "NilClass"
45
- "null"
46
- when "Symbol"
47
- "string"
48
- when "Date", "DateTime", "Time"
49
- "string"
36
+ when 'String'
37
+ 'string'
38
+ when 'Integer'
39
+ 'int'
40
+ when 'Float'
41
+ 'float'
42
+ when 'TrueClass', 'FalseClass'
43
+ 'bool'
44
+ when 'NilClass'
45
+ 'null'
46
+ when 'Symbol'
47
+ 'string'
48
+ when 'Date', 'DateTime', 'Time'
49
+ 'string'
50
50
  else
51
51
  # Check if it's a T::Struct or T::Enum
52
52
  if raw_type < T::Struct || raw_type < T::Enum
53
53
  type_name = raw_type.name || raw_type.to_s
54
- type_name.split('::').last || "unknown"
54
+ type_name.split('::').last || 'unknown'
55
55
  else
56
- "unknown"
56
+ 'unknown'
57
57
  end
58
58
  end
59
59
  end
@@ -63,28 +63,26 @@ module SorbetBaml
63
63
  types = type_object.types
64
64
  nil_type = types.find { |t| t.raw_type == NilClass }
65
65
  non_nil_types = types.reject { |t| t.raw_type == NilClass }
66
-
67
- if non_nil_types.empty?
68
- return "null"
69
- end
70
-
66
+
67
+ return 'null' if non_nil_types.empty?
68
+
71
69
  if non_nil_types.size == 1
72
70
  # This is T.nilable(T) - single type with nil
73
71
  base_type = map_type(non_nil_types.first)
74
72
  return nil_type ? "#{base_type}?" : base_type
75
73
  end
76
-
74
+
77
75
  # This is T.any with multiple types
78
76
  mapped_types = non_nil_types.map { |t| map_type(t) }
79
-
77
+
80
78
  # Special case: TrueClass + FalseClass = bool
81
- if mapped_types.sort == ["bool", "bool"]
82
- union_string = "bool"
83
- else
84
- # Remove duplicates and join
85
- union_string = mapped_types.uniq.join(" | ")
86
- end
87
-
79
+ union_string = if mapped_types.sort == %w[bool bool]
80
+ 'bool'
81
+ else
82
+ # Remove duplicates and join
83
+ mapped_types.uniq.join(' | ')
84
+ end
85
+
88
86
  # If nil is present, wrap in parentheses and add ?
89
87
  if nil_type
90
88
  "(#{union_string})?"
@@ -96,9 +94,9 @@ module SorbetBaml
96
94
  sig { params(type_object: T.untyped).returns(String) }
97
95
  def self.map_array_type(type_object)
98
96
  element_type = map_type(type_object.type)
99
-
97
+
100
98
  # If element type contains union (|), wrap in parentheses for correct precedence
101
- if element_type.include?("|")
99
+ if element_type.include?('|')
102
100
  "(#{element_type})[]"
103
101
  else
104
102
  "#{element_type}[]"
@@ -113,4 +111,4 @@ module SorbetBaml
113
111
  "map<#{key_type}, #{value_type}>"
114
112
  end
115
113
  end
116
- end
114
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SorbetBaml
4
- VERSION = "0.1.0"
4
+ VERSION = '0.3.0'
5
5
  end