dexkit 0.9.0 → 0.11.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/README.md +63 -254
  4. data/gemfiles/mongoid_no_ar.gemfile.lock +2 -2
  5. data/guides/llm/EVENT.md +25 -26
  6. data/guides/llm/FORM.md +200 -59
  7. data/guides/llm/OPERATION.md +115 -57
  8. data/guides/llm/QUERY.md +56 -0
  9. data/guides/llm/TOOL.md +308 -0
  10. data/lib/dex/context_dsl.rb +56 -0
  11. data/lib/dex/context_setup.rb +2 -33
  12. data/lib/dex/event/bus.rb +79 -11
  13. data/lib/dex/event/handler.rb +18 -1
  14. data/lib/dex/event/metadata.rb +15 -20
  15. data/lib/dex/event/processor.rb +2 -16
  16. data/lib/dex/event/test_helpers.rb +1 -1
  17. data/lib/dex/event.rb +3 -10
  18. data/lib/dex/form/context.rb +27 -0
  19. data/lib/dex/form/export.rb +128 -0
  20. data/lib/dex/form/nesting.rb +2 -0
  21. data/lib/dex/form.rb +119 -3
  22. data/lib/dex/id.rb +125 -0
  23. data/lib/dex/operation/async_proxy.rb +22 -4
  24. data/lib/dex/operation/guard_wrapper.rb +1 -1
  25. data/lib/dex/operation/jobs.rb +5 -4
  26. data/lib/dex/operation/once_wrapper.rb +1 -0
  27. data/lib/dex/operation/outcome.rb +14 -0
  28. data/lib/dex/operation/record_backend.rb +2 -1
  29. data/lib/dex/operation/record_wrapper.rb +14 -4
  30. data/lib/dex/operation/result_wrapper.rb +0 -12
  31. data/lib/dex/operation/test_helpers/assertions.rb +0 -88
  32. data/lib/dex/operation/test_helpers.rb +11 -1
  33. data/lib/dex/operation/ticket.rb +268 -0
  34. data/lib/dex/operation/trace_wrapper.rb +20 -0
  35. data/lib/dex/operation.rb +3 -0
  36. data/lib/dex/operation_failed.rb +14 -0
  37. data/lib/dex/query/export.rb +64 -0
  38. data/lib/dex/query.rb +41 -0
  39. data/lib/dex/test_log.rb +62 -4
  40. data/lib/dex/timeout.rb +14 -0
  41. data/lib/dex/tool.rb +388 -5
  42. data/lib/dex/trace.rb +291 -0
  43. data/lib/dex/version.rb +1 -1
  44. data/lib/dexkit.rb +22 -3
  45. metadata +12 -3
  46. data/lib/dex/event/trace.rb +0 -56
  47. data/lib/dex/event_test_helpers.rb +0 -3
data/lib/dex/trace.rb ADDED
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dex
4
+ module Trace
5
+ FIBER_KEY = :_dex_trace
6
+ FRAME_TYPES = %i[actor operation handler].freeze
7
+
8
+ class << self
9
+ def start(actor: nil, trace_id: nil)
10
+ previous = _dump_state
11
+ _set_state(trace_id: (trace_id || Dex::Id.generate("tr_")).to_s, frames: [], event_context: nil)
12
+ _state[:frames] << _normalize_actor(actor) if actor
13
+ yield
14
+ ensure
15
+ _restore_state(previous)
16
+ end
17
+
18
+ def ensure_started!(trace_id: nil)
19
+ return false if active?
20
+
21
+ _state[:trace_id] = (trace_id || Dex::Id.generate("tr_")).to_s
22
+ true
23
+ end
24
+
25
+ def active?
26
+ !trace_id.nil?
27
+ end
28
+
29
+ def with_frame(frame)
30
+ auto_started = ensure_started!
31
+ pushed = false
32
+ push(frame)
33
+ pushed = true
34
+ yield
35
+ ensure
36
+ pop if pushed
37
+ stop! if auto_started
38
+ end
39
+
40
+ def with_event_context(event)
41
+ auto_started = ensure_started!(trace_id: event.trace_id)
42
+ previous = _state[:event_context]
43
+ _state[:event_context] = _build_event_context(event)
44
+ yield
45
+ ensure
46
+ _state[:event_context] = previous
47
+ stop! if auto_started
48
+ end
49
+
50
+ def restore_event_context(event_id:, trace_id:, event_class: nil, event_ancestry: [])
51
+ previous = _dump_state
52
+ effective_trace_id = trace_id&.to_s || _state[:trace_id]
53
+ _set_state(
54
+ trace_id: effective_trace_id,
55
+ frames: _normalize_frames(_state[:frames]),
56
+ event_context: {
57
+ id: event_id&.to_s,
58
+ trace_id: effective_trace_id,
59
+ event_class: event_class,
60
+ event_ancestry: Array(event_ancestry).compact.map(&:to_s)
61
+ }
62
+ )
63
+ yield
64
+ ensure
65
+ _restore_state(previous)
66
+ end
67
+
68
+ def push(frame)
69
+ _state[:frames] << _normalize_frame(frame)
70
+ end
71
+
72
+ def pop
73
+ _state[:frames].pop
74
+ end
75
+
76
+ def current
77
+ _deep_copy(_state[:frames])
78
+ end
79
+
80
+ def trace_id
81
+ _state[:trace_id]
82
+ end
83
+
84
+ def current_id
85
+ _state[:frames].last&.dig(:id)
86
+ end
87
+
88
+ def actor
89
+ frame = _state[:frames].find { |entry| entry[:type] == :actor }
90
+ frame ? _deep_copy(frame) : nil
91
+ end
92
+
93
+ def current_event_id
94
+ current_event_context&.dig(:id)
95
+ end
96
+
97
+ def current_event_context
98
+ context = _state[:event_context]
99
+ return _deep_copy(context) if context
100
+
101
+ handler = _state[:frames].reverse.find { |frame| frame[:type] == :handler && frame[:event_id] }
102
+ return nil unless handler
103
+
104
+ {
105
+ id: handler[:event_id],
106
+ trace_id: trace_id,
107
+ event_class: handler[:event_class],
108
+ event_ancestry: _deep_copy(Array(handler[:event_ancestry]))
109
+ }
110
+ end
111
+
112
+ def dump
113
+ return nil unless active?
114
+
115
+ data = {
116
+ trace_id: trace_id,
117
+ frames: current
118
+ }
119
+ data[:event_context] = _deep_copy(_state[:event_context]) if _state[:event_context]
120
+ data
121
+ end
122
+
123
+ def restore(data)
124
+ return yield unless data
125
+
126
+ previous = _dump_state
127
+ _set_state(
128
+ trace_id: _fetch(data, :trace_id)&.to_s,
129
+ frames: _normalize_frames(_fetch(data, :frames)),
130
+ event_context: _normalize_event_context(_fetch(data, :event_context))
131
+ )
132
+ yield
133
+ ensure
134
+ _restore_state(previous)
135
+ end
136
+
137
+ def to_s
138
+ current.filter_map { |frame| _format_frame(frame) }.join(" > ")
139
+ end
140
+
141
+ def clear!
142
+ Fiber[FIBER_KEY] = nil
143
+ end
144
+ alias_method :stop!, :clear!
145
+
146
+ private
147
+
148
+ def _state
149
+ Fiber[FIBER_KEY] ||= { trace_id: nil, frames: [], event_context: nil }
150
+ end
151
+
152
+ def _set_state(trace_id:, frames:, event_context:)
153
+ Fiber[FIBER_KEY] = {
154
+ trace_id: trace_id,
155
+ frames: frames,
156
+ event_context: event_context
157
+ }
158
+ end
159
+
160
+ def _dump_state
161
+ state = _state
162
+ return nil unless state[:trace_id] || !state[:frames].empty? || state[:event_context]
163
+
164
+ {
165
+ trace_id: state[:trace_id],
166
+ frames: _deep_copy(state[:frames]),
167
+ event_context: _deep_copy(state[:event_context])
168
+ }
169
+ end
170
+
171
+ def _restore_state(state)
172
+ if state
173
+ _set_state(
174
+ trace_id: state[:trace_id],
175
+ frames: _normalize_frames(state[:frames]),
176
+ event_context: _normalize_event_context(state[:event_context])
177
+ )
178
+ else
179
+ clear!
180
+ end
181
+ end
182
+
183
+ def _normalize_frames(frames)
184
+ Array(frames).map { |frame| _normalize_frame(frame) }
185
+ end
186
+
187
+ def _normalize_actor(actor)
188
+ raise ArgumentError, "actor must be a Hash" unless actor.is_a?(Hash)
189
+
190
+ normalized = _symbolize(actor)
191
+ actor_type = normalized[:type]
192
+ raise ArgumentError, "actor must include :type" if actor_type.nil? || actor_type.to_s.strip.empty?
193
+
194
+ frame = { type: :actor, actor_type: actor_type.to_s }
195
+ frame[:id] = normalized[:id].to_s if normalized.key?(:id) && !normalized[:id].nil?
196
+
197
+ normalized.each do |key, value|
198
+ next if %i[type id].include?(key)
199
+
200
+ frame[key] = _deep_copy(value)
201
+ end
202
+
203
+ frame
204
+ end
205
+
206
+ def _normalize_frame(frame)
207
+ raise ArgumentError, "trace frame must be a Hash" unless frame.is_a?(Hash)
208
+
209
+ normalized = _symbolize(frame)
210
+ type = normalized[:type]&.to_sym
211
+ raise ArgumentError, "trace frame type is required" unless type
212
+ raise ArgumentError, "unknown trace frame type: #{type.inspect}" unless FRAME_TYPES.include?(type)
213
+
214
+ normalized[:type] = type
215
+ normalized[:id] = normalized[:id].to_s if normalized.key?(:id) && !normalized[:id].nil?
216
+ normalized[:actor_type] = normalized[:actor_type].to_s if normalized.key?(:actor_type) && !normalized[:actor_type].nil?
217
+ normalized[:event_id] = normalized[:event_id].to_s if normalized.key?(:event_id) && !normalized[:event_id].nil?
218
+ normalized[:event_ancestry] = Array(normalized[:event_ancestry]).compact.map(&:to_s) if normalized.key?(:event_ancestry)
219
+ normalized
220
+ end
221
+
222
+ def _normalize_event_context(context)
223
+ return nil unless context
224
+
225
+ {
226
+ id: _fetch(context, :id)&.to_s,
227
+ trace_id: _fetch(context, :trace_id)&.to_s,
228
+ event_class: _fetch(context, :event_class),
229
+ event_ancestry: Array(_fetch(context, :event_ancestry)).compact.map(&:to_s)
230
+ }
231
+ end
232
+
233
+ def _build_event_context(event)
234
+ {
235
+ id: event.id.to_s,
236
+ trace_id: event.trace_id.to_s,
237
+ event_class: event.class.name,
238
+ event_ancestry: Array(event.metadata.event_ancestry).compact.map(&:to_s)
239
+ }
240
+ end
241
+
242
+ def _symbolize(hash)
243
+ hash.each_with_object({}) do |(key, value), result|
244
+ result[key.respond_to?(:to_sym) ? key.to_sym : key] = _deep_copy(value)
245
+ end
246
+ end
247
+
248
+ def _deep_copy(value)
249
+ case value
250
+ when Hash
251
+ value.each_with_object({}) { |(key, nested), result| result[key] = _deep_copy(nested) }
252
+ when Array
253
+ value.map { |nested| _deep_copy(nested) }
254
+ else
255
+ value
256
+ end
257
+ end
258
+
259
+ def _fetch(hash, key)
260
+ hash[key] || hash[key.to_s]
261
+ end
262
+
263
+ def _format_frame(frame)
264
+ case frame[:type]
265
+ when :actor
266
+ return nil unless frame[:actor_type]
267
+
268
+ frame[:id] ? "#{frame[:actor_type]}:#{frame[:id]}" : frame[:actor_type]
269
+ when :operation
270
+ name = frame[:class] || "Operation"
271
+ id = _display_id(frame[:id])
272
+ id ? "#{name}(#{id})" : name
273
+ when :handler
274
+ event_class = frame[:event_class] || "Event"
275
+ handler_name = frame[:class] || "Handler"
276
+ id = _display_id(frame[:id])
277
+ id ? "[#{event_class}] #{handler_name}(#{id})" : "[#{event_class}] #{handler_name}"
278
+ end
279
+ end
280
+
281
+ def _display_id(id)
282
+ return nil unless id
283
+
284
+ prefix, suffix = id.split("_", 2)
285
+ return id[0, 10] unless suffix
286
+
287
+ "#{prefix}_#{suffix[0, 7]}"
288
+ end
289
+ end
290
+ end
291
+ end
data/lib/dex/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dex
4
- VERSION = "0.9.0"
4
+ VERSION = "0.11.0"
5
5
  end
data/lib/dexkit.rb CHANGED
@@ -10,13 +10,18 @@ require_relative "dex/concern"
10
10
  require_relative "dex/ref_type"
11
11
  require_relative "dex/type_coercion"
12
12
  require_relative "dex/props_setup"
13
+ require_relative "dex/context_dsl"
13
14
  require_relative "dex/context_setup"
14
15
  require_relative "dex/error"
16
+ require_relative "dex/operation_failed"
17
+ require_relative "dex/timeout"
15
18
  require_relative "dex/settings"
16
19
  require_relative "dex/pipeline"
17
20
  require_relative "dex/executable"
18
21
  require_relative "dex/registry"
19
22
  require_relative "dex/type_serializer"
23
+ require_relative "dex/id"
24
+ require_relative "dex/trace"
20
25
  require_relative "dex/operation"
21
26
  require_relative "dex/event"
22
27
  require_relative "dex/tool"
@@ -25,15 +30,13 @@ require_relative "dex/query"
25
30
 
26
31
  module Dex
27
32
  class Configuration
28
- attr_accessor :record_class, :event_store, :event_context, :restore_event_context
33
+ attr_accessor :record_class, :event_store
29
34
  attr_reader :transaction_adapter
30
35
 
31
36
  def initialize
32
37
  @record_class = nil
33
38
  @transaction_adapter = nil
34
39
  @event_store = nil
35
- @event_context = nil
36
- @restore_event_context = nil
37
40
  end
38
41
 
39
42
  def transaction_adapter=(adapter)
@@ -77,6 +80,22 @@ module Dex
77
80
  configuration.transaction_adapter = adapter
78
81
  end
79
82
 
83
+ def actor
84
+ frame = Dex::Trace.actor
85
+ return nil unless frame
86
+
87
+ payload = frame.dup
88
+ payload.delete(:type)
89
+ payload[:type] = payload.delete(:actor_type) if payload.key?(:actor_type)
90
+ payload
91
+ end
92
+
93
+ def system(name = nil)
94
+ h = { type: :system }
95
+ h[:name] = name.to_s if name
96
+ h.freeze
97
+ end
98
+
80
99
  CONTEXT_KEY = :_dex_context
81
100
  EMPTY_CONTEXT = {}.freeze
82
101
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dexkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacek Galanciak
@@ -167,7 +167,9 @@ files:
167
167
  - guides/llm/FORM.md
168
168
  - guides/llm/OPERATION.md
169
169
  - guides/llm/QUERY.md
170
+ - guides/llm/TOOL.md
170
171
  - lib/dex/concern.rb
172
+ - lib/dex/context_dsl.rb
171
173
  - lib/dex/context_setup.rb
172
174
  - lib/dex/error.rb
173
175
  - lib/dex/event.rb
@@ -180,12 +182,13 @@ files:
180
182
  - lib/dex/event/suppression.rb
181
183
  - lib/dex/event/test_helpers.rb
182
184
  - lib/dex/event/test_helpers/assertions.rb
183
- - lib/dex/event/trace.rb
184
- - lib/dex/event_test_helpers.rb
185
185
  - lib/dex/executable.rb
186
186
  - lib/dex/form.rb
187
+ - lib/dex/form/context.rb
188
+ - lib/dex/form/export.rb
187
189
  - lib/dex/form/nesting.rb
188
190
  - lib/dex/form/uniqueness_validator.rb
191
+ - lib/dex/id.rb
189
192
  - lib/dex/match.rb
190
193
  - lib/dex/operation.rb
191
194
  - lib/dex/operation/async_proxy.rb
@@ -207,12 +210,16 @@ files:
207
210
  - lib/dex/operation/test_helpers/assertions.rb
208
211
  - lib/dex/operation/test_helpers/execution.rb
209
212
  - lib/dex/operation/test_helpers/stubbing.rb
213
+ - lib/dex/operation/ticket.rb
214
+ - lib/dex/operation/trace_wrapper.rb
210
215
  - lib/dex/operation/transaction_adapter.rb
211
216
  - lib/dex/operation/transaction_wrapper.rb
217
+ - lib/dex/operation_failed.rb
212
218
  - lib/dex/pipeline.rb
213
219
  - lib/dex/props_setup.rb
214
220
  - lib/dex/query.rb
215
221
  - lib/dex/query/backend.rb
222
+ - lib/dex/query/export.rb
216
223
  - lib/dex/query/filtering.rb
217
224
  - lib/dex/query/sorting.rb
218
225
  - lib/dex/railtie.rb
@@ -221,7 +228,9 @@ files:
221
228
  - lib/dex/settings.rb
222
229
  - lib/dex/test_helpers.rb
223
230
  - lib/dex/test_log.rb
231
+ - lib/dex/timeout.rb
224
232
  - lib/dex/tool.rb
233
+ - lib/dex/trace.rb
225
234
  - lib/dex/type_coercion.rb
226
235
  - lib/dex/type_serializer.rb
227
236
  - lib/dex/version.rb
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dex
4
- class Event
5
- module Trace
6
- STACK_KEY = :_dex_event_trace_stack
7
-
8
- class << self
9
- include ExecutionState
10
-
11
- def with_event(event, &block)
12
- stack = _stack
13
- stack.push(event.trace_frame)
14
- yield
15
- ensure
16
- stack.pop
17
- end
18
-
19
- def current_event_id
20
- _stack.last&.dig(:id)
21
- end
22
-
23
- def current_trace_id
24
- _stack.last&.dig(:trace_id)
25
- end
26
-
27
- def dump
28
- frame = _stack.last
29
- return nil unless frame
30
-
31
- { id: frame[:id], trace_id: frame[:trace_id] }
32
- end
33
-
34
- def restore(data, &block)
35
- return yield unless data
36
-
37
- stack = _stack
38
- stack.push(data)
39
- yield
40
- ensure
41
- stack.pop if data
42
- end
43
-
44
- def clear!
45
- _execution_state[STACK_KEY] = []
46
- end
47
-
48
- private
49
-
50
- def _stack
51
- _execution_state[STACK_KEY] ||= []
52
- end
53
- end
54
- end
55
- end
56
- end
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "event/test_helpers"