dexkit 0.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -2
- data/README.md +62 -281
- data/gemfiles/mongoid_no_ar.gemfile.lock +2 -2
- data/guides/llm/EVENT.md +1 -7
- data/guides/llm/OPERATION.md +88 -54
- data/guides/llm/QUERY.md +6 -0
- data/guides/llm/TOOL.md +308 -0
- data/lib/dex/event/bus.rb +1 -3
- data/lib/dex/event/handler.rb +1 -2
- data/lib/dex/event/metadata.rb +3 -15
- data/lib/dex/event/processor.rb +1 -15
- data/lib/dex/event.rb +1 -3
- data/lib/dex/id.rb +92 -5
- data/lib/dex/operation/async_proxy.rb +10 -2
- data/lib/dex/operation/guard_wrapper.rb +1 -1
- data/lib/dex/operation/outcome.rb +14 -0
- data/lib/dex/operation/result_wrapper.rb +0 -12
- data/lib/dex/operation/test_helpers/assertions.rb +0 -112
- data/lib/dex/operation/ticket.rb +268 -0
- data/lib/dex/operation.rb +1 -0
- data/lib/dex/operation_failed.rb +14 -0
- data/lib/dex/timeout.rb +14 -0
- data/lib/dex/tool.rb +388 -5
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +19 -3
- metadata +5 -3
- data/lib/dex/event/trace.rb +0 -43
- data/lib/dex/event_test_helpers.rb +0 -3
data/lib/dex/tool.rb
CHANGED
|
@@ -2,11 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
module Dex
|
|
4
4
|
module Tool
|
|
5
|
+
QUERY_TOOL_OPTIONS = %i[scope serialize limit only_filters except_filters only_sorts].freeze
|
|
6
|
+
|
|
5
7
|
module_function
|
|
6
8
|
|
|
7
|
-
def from(
|
|
9
|
+
def from(klass, **opts)
|
|
8
10
|
_require_ruby_llm!
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
if klass < Dex::Query
|
|
13
|
+
_validate_query_tool_options!(klass, opts)
|
|
14
|
+
_build_query_tool(klass, **opts)
|
|
15
|
+
elsif klass < Dex::Operation
|
|
16
|
+
_reject_unknown_options!(klass, opts)
|
|
17
|
+
_build_tool(klass)
|
|
18
|
+
else
|
|
19
|
+
raise ArgumentError, "expected a Dex::Operation or Dex::Query subclass, got #{klass}"
|
|
20
|
+
end
|
|
10
21
|
end
|
|
11
22
|
|
|
12
23
|
def all
|
|
@@ -35,7 +46,9 @@ module Dex
|
|
|
35
46
|
"Dex::Tool requires the ruby-llm gem. Add `gem 'ruby_llm'` to your Gemfile."
|
|
36
47
|
end
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
# --- Operation tools ---
|
|
50
|
+
|
|
51
|
+
def _build_tool(operation_class)
|
|
39
52
|
op = operation_class
|
|
40
53
|
schema = op.contract.to_json_schema
|
|
41
54
|
tool_description = _tool_description(op)
|
|
@@ -77,7 +90,374 @@ module Dex
|
|
|
77
90
|
parts.join("\n")
|
|
78
91
|
end
|
|
79
92
|
|
|
80
|
-
def
|
|
93
|
+
def _reject_unknown_options!(_klass, opts)
|
|
94
|
+
return if opts.empty?
|
|
95
|
+
|
|
96
|
+
raise ArgumentError, "#{opts.keys.first}: is not a valid option for Operation tools"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# --- Query tools ---
|
|
100
|
+
|
|
101
|
+
def _validate_query_tool_options!(klass, opts)
|
|
102
|
+
unknown = opts.keys - QUERY_TOOL_OPTIONS
|
|
103
|
+
unless unknown.empty?
|
|
104
|
+
raise ArgumentError, "unknown option#{"s" if unknown.size > 1}: #{unknown.map { |k| "#{k}:" }.join(", ")}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
label = klass.name || klass.inspect
|
|
108
|
+
|
|
109
|
+
unless opts.key?(:scope)
|
|
110
|
+
raise ArgumentError, "#{label} is a Dex::Query. Query tools require scope: and serialize:.\n" \
|
|
111
|
+
"Example: Dex::Tool.from(#{label},\n" \
|
|
112
|
+
" scope: -> { Current.user.orders },\n" \
|
|
113
|
+
" serialize: ->(record) { record.as_json(only: [:id, :name]) })"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
unless opts.key?(:serialize)
|
|
117
|
+
raise ArgumentError, "#{label} is a Dex::Query. Query tools require serialize:.\n" \
|
|
118
|
+
"Example: Dex::Tool.from(#{label},\n" \
|
|
119
|
+
" scope: -> { Current.user.orders },\n" \
|
|
120
|
+
" serialize: ->(record) { record.as_json(only: [:id, :name]) })"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
unless klass._scope_block
|
|
124
|
+
raise ArgumentError, "#{label} has no scope block. Define scope { ... } in the query class."
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
unless opts[:scope].respond_to?(:call)
|
|
128
|
+
raise ArgumentError, "scope: must respond to call (lambda or proc)"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
unless opts[:serialize].respond_to?(:call)
|
|
132
|
+
raise ArgumentError, "serialize: must respond to call (lambda or proc)"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if opts.key?(:limit)
|
|
136
|
+
unless opts[:limit].is_a?(Integer) && opts[:limit] > 0
|
|
137
|
+
raise ArgumentError, "limit: must be a positive integer"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if opts.key?(:only_filters) && opts.key?(:except_filters)
|
|
142
|
+
raise ArgumentError, "only_filters: and except_filters: are mutually exclusive"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
declared_filters = klass.filters
|
|
146
|
+
if opts.key?(:only_filters)
|
|
147
|
+
context_mapped = klass.context_mappings.keys.to_set
|
|
148
|
+
opts[:only_filters].each do |f|
|
|
149
|
+
f_sym = f.to_sym
|
|
150
|
+
unless declared_filters.include?(f_sym)
|
|
151
|
+
raise ArgumentError, "unknown filter :#{f}. Declared: #{declared_filters.inspect}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if context_mapped.include?(f_sym)
|
|
155
|
+
raise ArgumentError, "filter :#{f} is context-mapped and automatically excluded from " \
|
|
156
|
+
"tool schema. Remove it from only_filters:"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
prop = klass.literal_properties.find { |p| p.name == f_sym }
|
|
160
|
+
if prop && _ref_type?(prop.type)
|
|
161
|
+
raise ArgumentError, "filter :#{f} backs a _Ref prop and is automatically excluded from " \
|
|
162
|
+
"tool schema. Remove it from only_filters:"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
if opts.key?(:except_filters)
|
|
168
|
+
opts[:except_filters].each do |f|
|
|
169
|
+
unless declared_filters.include?(f.to_sym)
|
|
170
|
+
raise ArgumentError, "unknown filter :#{f} in except_filters. Declared: #{declared_filters.inspect}"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
declared_sorts = klass.sorts
|
|
176
|
+
if opts.key?(:only_sorts)
|
|
177
|
+
opts[:only_sorts].each do |s|
|
|
178
|
+
unless declared_sorts.include?(s.to_sym)
|
|
179
|
+
raise ArgumentError, "unknown sort :#{s}. Declared: #{declared_sorts.inspect}"
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
default_sort = klass._sort_default
|
|
184
|
+
if default_sort
|
|
185
|
+
bare = default_sort.delete_prefix("-").to_sym
|
|
186
|
+
unless opts[:only_sorts].map(&:to_sym).include?(bare)
|
|
187
|
+
raise ArgumentError, "query default sort #{default_sort} is not in only_sorts. " \
|
|
188
|
+
"Include it or change the query's default"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
if klass.respond_to?(:literal_properties)
|
|
194
|
+
klass.literal_properties.each do |prop|
|
|
195
|
+
if %i[limit offset].include?(prop.name)
|
|
196
|
+
raise ArgumentError, "#{label} declares prop :#{prop.name} which conflicts with " \
|
|
197
|
+
"the tool's pagination parameter. Rename the prop"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
_validate_satisfiable_props!(klass, opts)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def _validate_satisfiable_props!(klass, opts)
|
|
206
|
+
return unless klass.respond_to?(:literal_properties)
|
|
207
|
+
|
|
208
|
+
context_keys = klass.context_mappings.keys.to_set
|
|
209
|
+
visible = _compute_visible_filters(klass, opts[:only_filters], opts[:except_filters])
|
|
210
|
+
excluded = _query_excluded_prop_names(klass, visible)
|
|
211
|
+
|
|
212
|
+
klass.literal_properties.each do |prop|
|
|
213
|
+
next unless excluded.include?(prop.name)
|
|
214
|
+
next unless prop.required?
|
|
215
|
+
next if context_keys.include?(prop.name)
|
|
216
|
+
|
|
217
|
+
if _ref_type?(prop.type)
|
|
218
|
+
raise ArgumentError, "prop :#{prop.name} (_Ref) is auto-excluded from the tool schema " \
|
|
219
|
+
"but is required with no default and no context mapping — the tool could never execute"
|
|
220
|
+
else
|
|
221
|
+
raise ArgumentError, "excluding filter :#{prop.name} hides required prop :#{prop.name} " \
|
|
222
|
+
"which has no default and no context mapping — the tool could never execute"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def _build_query_tool(klass, scope:, serialize:, limit: 50, only_filters: nil, except_filters: nil, only_sorts: nil)
|
|
228
|
+
max_limit = limit
|
|
229
|
+
scope_lambda = scope
|
|
230
|
+
serialize_lambda = serialize
|
|
231
|
+
|
|
232
|
+
visible_filters = _compute_visible_filters(klass, only_filters, except_filters)
|
|
233
|
+
allowed_sorts = only_sorts ? only_sorts.map(&:to_sym) : klass.sorts
|
|
234
|
+
|
|
235
|
+
schema = _query_params_schema(klass, visible_filters, allowed_sorts, max_limit)
|
|
236
|
+
tool_desc = _query_tool_description(klass, visible_filters, allowed_sorts, max_limit)
|
|
237
|
+
tool_name = "dex_query_#{(klass.name || "query").gsub("::", "_").downcase}"
|
|
238
|
+
|
|
239
|
+
stripped_param_keys = klass.context_mappings.keys.to_set
|
|
240
|
+
invisible_filters = klass.filters.to_set - visible_filters.to_set
|
|
241
|
+
stripped_param_keys.merge(invisible_filters)
|
|
242
|
+
|
|
243
|
+
query_class = klass
|
|
244
|
+
|
|
245
|
+
Class.new(RubyLLM::Tool) do
|
|
246
|
+
define_method(:name) { tool_name }
|
|
247
|
+
define_method(:description) { tool_desc }
|
|
248
|
+
define_method(:params_schema) { schema }
|
|
249
|
+
|
|
250
|
+
define_method(:execute) do |**params|
|
|
251
|
+
params = params.transform_keys(&:to_sym)
|
|
252
|
+
|
|
253
|
+
req_limit = [(params.delete(:limit) || max_limit).to_i, max_limit].min
|
|
254
|
+
req_limit = max_limit if req_limit <= 0
|
|
255
|
+
req_offset = [params.delete(:offset).to_i, 0].max
|
|
256
|
+
|
|
257
|
+
sort_value = params.delete(:sort)&.to_s
|
|
258
|
+
if sort_value && !sort_value.empty?
|
|
259
|
+
bare = sort_value.delete_prefix("-").to_sym
|
|
260
|
+
valid = allowed_sorts.include?(bare)
|
|
261
|
+
if valid && sort_value.start_with?("-")
|
|
262
|
+
sort_def = query_class._sort_registry[bare]
|
|
263
|
+
valid = false if sort_def&.custom
|
|
264
|
+
end
|
|
265
|
+
sort_value = nil unless valid
|
|
266
|
+
else
|
|
267
|
+
sort_value = nil
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
stripped_param_keys.each { |k| params.delete(k) }
|
|
271
|
+
|
|
272
|
+
injected_scope = scope_lambda.call
|
|
273
|
+
|
|
274
|
+
overrides = {}
|
|
275
|
+
overrides[:sort] = sort_value if sort_value
|
|
276
|
+
query = query_class.from_params(params, scope: injected_scope, **overrides)
|
|
277
|
+
|
|
278
|
+
records = query.resolve
|
|
279
|
+
|
|
280
|
+
total = begin
|
|
281
|
+
result = records.count
|
|
282
|
+
result.is_a?(Integer) ? result : nil
|
|
283
|
+
rescue
|
|
284
|
+
nil
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
records = records.offset(req_offset).limit(req_limit)
|
|
288
|
+
|
|
289
|
+
serialized = records.map { |r| serialize_lambda.call(r) }
|
|
290
|
+
|
|
291
|
+
{ records: serialized, total: total, limit: req_limit, offset: req_offset }
|
|
292
|
+
rescue ArgumentError, Literal::TypeError => e
|
|
293
|
+
{ error: "invalid_params", message: e.message }
|
|
294
|
+
rescue => e
|
|
295
|
+
{ error: "query_failed", message: e.message }
|
|
296
|
+
end
|
|
297
|
+
end.new
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def _query_tool_description(klass, visible_filters, allowed_sorts, max_limit)
|
|
301
|
+
parts = []
|
|
302
|
+
text = klass.description || klass.name || "Query"
|
|
303
|
+
parts << (text.end_with?(".") ? text : "#{text}.")
|
|
304
|
+
|
|
305
|
+
if visible_filters.any?
|
|
306
|
+
filter_descs = visible_filters.map { |f| _query_filter_desc(klass, f) }
|
|
307
|
+
parts << "Filters: #{filter_descs.join(", ")}."
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
if allowed_sorts.any?
|
|
311
|
+
default_sort = klass._sort_default
|
|
312
|
+
sort_descs = allowed_sorts.map do |s|
|
|
313
|
+
bare_default = default_sort&.delete_prefix("-")
|
|
314
|
+
if bare_default && bare_default.to_sym == s
|
|
315
|
+
"#{s} (default: #{default_sort})"
|
|
316
|
+
else
|
|
317
|
+
s.to_s
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
parts << "Sorts: #{sort_descs.join(", ")}."
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
parts << "Returns up to #{max_limit} results per page. Use offset to paginate."
|
|
324
|
+
|
|
325
|
+
parts.join("\n")
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def _query_filter_desc(klass, filter_name)
|
|
329
|
+
descs = klass.respond_to?(:prop_descriptions) ? klass.prop_descriptions : {}
|
|
330
|
+
prop_desc = descs[filter_name]
|
|
331
|
+
|
|
332
|
+
prop = klass.literal_properties.find { |p| p.name == filter_name }
|
|
333
|
+
return filter_name.to_s unless prop
|
|
334
|
+
|
|
335
|
+
inner_type = prop.type
|
|
336
|
+
inner_type = inner_type.type if inner_type.is_a?(Literal::Types::NilableType)
|
|
337
|
+
|
|
338
|
+
if inner_type.respond_to?(:primitives) && inner_type.primitives&.any?
|
|
339
|
+
values = inner_type.primitives.to_a.map(&:to_s)
|
|
340
|
+
"#{filter_name} (#{_join_with_or(values)})"
|
|
341
|
+
elsif prop_desc
|
|
342
|
+
"#{filter_name} (#{prop_desc})"
|
|
343
|
+
else
|
|
344
|
+
filter_name.to_s
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def _join_with_or(values)
|
|
349
|
+
case values.size
|
|
350
|
+
when 1 then values.first
|
|
351
|
+
when 2 then "#{values.first} or #{values.last}"
|
|
352
|
+
else "#{values[..-2].join(", ")}, or #{values.last}"
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def _query_params_schema(klass, visible_filters, allowed_sorts, max_limit)
|
|
357
|
+
properties = {}
|
|
358
|
+
required = []
|
|
359
|
+
|
|
360
|
+
excluded = _query_excluded_prop_names(klass, visible_filters)
|
|
361
|
+
descs = klass.respond_to?(:prop_descriptions) ? klass.prop_descriptions : {}
|
|
362
|
+
|
|
363
|
+
if klass.respond_to?(:literal_properties)
|
|
364
|
+
klass.literal_properties.each do |prop|
|
|
365
|
+
next if excluded.include?(prop.name)
|
|
366
|
+
|
|
367
|
+
prop_desc = descs[prop.name]
|
|
368
|
+
schema = TypeSerializer.to_json_schema(prop.type, desc: prop_desc)
|
|
369
|
+
properties[prop.name.to_s] = schema
|
|
370
|
+
required << prop.name.to_s if prop.required?
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
if allowed_sorts.any?
|
|
375
|
+
sort_values = []
|
|
376
|
+
allowed_sorts.each do |s|
|
|
377
|
+
sort_def = klass._sort_registry[s]
|
|
378
|
+
sort_values << s.to_s
|
|
379
|
+
sort_values << "-#{s}" unless sort_def.custom
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
default_sort = klass._sort_default
|
|
383
|
+
sort_desc = "Sort order. Prefix with - for descending."
|
|
384
|
+
sort_desc += " Default: #{default_sort}" if default_sort
|
|
385
|
+
|
|
386
|
+
properties["sort"] = {
|
|
387
|
+
type: "string",
|
|
388
|
+
enum: sort_values,
|
|
389
|
+
description: sort_desc
|
|
390
|
+
}
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
properties["limit"] = {
|
|
394
|
+
type: "integer",
|
|
395
|
+
description: "Maximum number of results (default: #{max_limit}, max: #{max_limit})"
|
|
396
|
+
}
|
|
397
|
+
properties["offset"] = {
|
|
398
|
+
type: "integer",
|
|
399
|
+
description: "Number of results to skip (default: 0)"
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
result = { "$schema": "https://json-schema.org/draft/2020-12/schema" }
|
|
403
|
+
result[:type] = "object"
|
|
404
|
+
result[:properties] = properties
|
|
405
|
+
result[:required] = required unless required.empty?
|
|
406
|
+
result[:additionalProperties] = false
|
|
407
|
+
result
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def _compute_visible_filters(klass, only_filters, except_filters)
|
|
411
|
+
context_keys = klass.context_mappings.keys.to_set
|
|
412
|
+
|
|
413
|
+
filters = klass.filters.dup
|
|
414
|
+
filters.reject! { |f| context_keys.include?(f) }
|
|
415
|
+
filters.reject! do |f|
|
|
416
|
+
prop = klass.literal_properties.find { |p| p.name == f }
|
|
417
|
+
prop && _ref_type?(prop.type)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
if only_filters
|
|
421
|
+
only_set = only_filters.map(&:to_sym).to_set
|
|
422
|
+
filters.select! { |f| only_set.include?(f) }
|
|
423
|
+
elsif except_filters
|
|
424
|
+
except_set = except_filters.map(&:to_sym).to_set
|
|
425
|
+
filters.reject! { |f| except_set.include?(f) }
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
filters
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def _query_excluded_prop_names(klass, visible_filters)
|
|
432
|
+
return Set.new unless klass.respond_to?(:literal_properties)
|
|
433
|
+
|
|
434
|
+
context_keys = klass.context_mappings.keys.to_set
|
|
435
|
+
visible_set = visible_filters.to_set
|
|
436
|
+
all_filters = klass.filters.to_set
|
|
437
|
+
|
|
438
|
+
excluded = Set.new
|
|
439
|
+
excluded.merge(context_keys)
|
|
440
|
+
|
|
441
|
+
klass.literal_properties.each do |prop|
|
|
442
|
+
excluded << prop.name if _ref_type?(prop.type)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
invisible_filters = all_filters - visible_set
|
|
446
|
+
excluded.merge(invisible_filters)
|
|
447
|
+
|
|
448
|
+
excluded
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def _ref_type?(type)
|
|
452
|
+
return true if type.is_a?(Dex::RefType)
|
|
453
|
+
return _ref_type?(type.type) if type.respond_to?(:type)
|
|
454
|
+
|
|
455
|
+
false
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# --- Explain tool ---
|
|
459
|
+
|
|
460
|
+
def _build_explain_tool
|
|
81
461
|
Class.new(RubyLLM::Tool) do
|
|
82
462
|
define_method(:name) { "dex_explain" }
|
|
83
463
|
define_method(:description) { "Check if an operation can be executed with given params, without running it." }
|
|
@@ -110,6 +490,9 @@ module Dex
|
|
|
110
490
|
end.new
|
|
111
491
|
end
|
|
112
492
|
|
|
113
|
-
private_class_method :_require_ruby_llm!, :_build_tool, :_tool_description, :_build_explain_tool
|
|
493
|
+
private_class_method :_require_ruby_llm!, :_build_tool, :_tool_description, :_build_explain_tool,
|
|
494
|
+
:_reject_unknown_options!, :_validate_query_tool_options!, :_validate_satisfiable_props!,
|
|
495
|
+
:_build_query_tool, :_query_tool_description, :_query_filter_desc, :_join_with_or,
|
|
496
|
+
:_query_params_schema, :_compute_visible_filters, :_query_excluded_prop_names, :_ref_type?
|
|
114
497
|
end
|
|
115
498
|
end
|
data/lib/dex/version.rb
CHANGED
data/lib/dexkit.rb
CHANGED
|
@@ -13,6 +13,8 @@ require_relative "dex/props_setup"
|
|
|
13
13
|
require_relative "dex/context_dsl"
|
|
14
14
|
require_relative "dex/context_setup"
|
|
15
15
|
require_relative "dex/error"
|
|
16
|
+
require_relative "dex/operation_failed"
|
|
17
|
+
require_relative "dex/timeout"
|
|
16
18
|
require_relative "dex/settings"
|
|
17
19
|
require_relative "dex/pipeline"
|
|
18
20
|
require_relative "dex/executable"
|
|
@@ -28,15 +30,13 @@ require_relative "dex/query"
|
|
|
28
30
|
|
|
29
31
|
module Dex
|
|
30
32
|
class Configuration
|
|
31
|
-
attr_accessor :record_class, :event_store
|
|
33
|
+
attr_accessor :record_class, :event_store
|
|
32
34
|
attr_reader :transaction_adapter
|
|
33
35
|
|
|
34
36
|
def initialize
|
|
35
37
|
@record_class = nil
|
|
36
38
|
@transaction_adapter = nil
|
|
37
39
|
@event_store = nil
|
|
38
|
-
@event_context = nil
|
|
39
|
-
@restore_event_context = nil
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def transaction_adapter=(adapter)
|
|
@@ -80,6 +80,22 @@ module Dex
|
|
|
80
80
|
configuration.transaction_adapter = adapter
|
|
81
81
|
end
|
|
82
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
|
+
|
|
83
99
|
CONTEXT_KEY = :_dex_context
|
|
84
100
|
EMPTY_CONTEXT = {}.freeze
|
|
85
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.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jacek Galanciak
|
|
@@ -167,6 +167,7 @@ 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
|
|
171
172
|
- lib/dex/context_dsl.rb
|
|
172
173
|
- lib/dex/context_setup.rb
|
|
@@ -181,8 +182,6 @@ files:
|
|
|
181
182
|
- lib/dex/event/suppression.rb
|
|
182
183
|
- lib/dex/event/test_helpers.rb
|
|
183
184
|
- lib/dex/event/test_helpers/assertions.rb
|
|
184
|
-
- lib/dex/event/trace.rb
|
|
185
|
-
- lib/dex/event_test_helpers.rb
|
|
186
185
|
- lib/dex/executable.rb
|
|
187
186
|
- lib/dex/form.rb
|
|
188
187
|
- lib/dex/form/context.rb
|
|
@@ -211,9 +210,11 @@ files:
|
|
|
211
210
|
- lib/dex/operation/test_helpers/assertions.rb
|
|
212
211
|
- lib/dex/operation/test_helpers/execution.rb
|
|
213
212
|
- lib/dex/operation/test_helpers/stubbing.rb
|
|
213
|
+
- lib/dex/operation/ticket.rb
|
|
214
214
|
- lib/dex/operation/trace_wrapper.rb
|
|
215
215
|
- lib/dex/operation/transaction_adapter.rb
|
|
216
216
|
- lib/dex/operation/transaction_wrapper.rb
|
|
217
|
+
- lib/dex/operation_failed.rb
|
|
217
218
|
- lib/dex/pipeline.rb
|
|
218
219
|
- lib/dex/props_setup.rb
|
|
219
220
|
- lib/dex/query.rb
|
|
@@ -227,6 +228,7 @@ files:
|
|
|
227
228
|
- lib/dex/settings.rb
|
|
228
229
|
- lib/dex/test_helpers.rb
|
|
229
230
|
- lib/dex/test_log.rb
|
|
231
|
+
- lib/dex/timeout.rb
|
|
230
232
|
- lib/dex/tool.rb
|
|
231
233
|
- lib/dex/trace.rb
|
|
232
234
|
- lib/dex/type_coercion.rb
|
data/lib/dex/event/trace.rb
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dex
|
|
4
|
-
class Event
|
|
5
|
-
module Trace
|
|
6
|
-
class << self
|
|
7
|
-
def with_event(event, &block)
|
|
8
|
-
Dex::Trace.with_event_context(event, &block)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def current_event_id
|
|
12
|
-
Dex::Trace.current_event_id
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def current_trace_id
|
|
16
|
-
Dex::Trace.trace_id
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def dump
|
|
20
|
-
Dex::Trace.dump
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def restore(data, &block)
|
|
24
|
-
return yield unless data
|
|
25
|
-
|
|
26
|
-
if data.is_a?(Hash) && (data.key?(:frames) || data.key?("frames"))
|
|
27
|
-
Dex::Trace.restore(data, &block)
|
|
28
|
-
else
|
|
29
|
-
Dex::Trace.restore_event_context(
|
|
30
|
-
event_id: data[:id] || data["id"],
|
|
31
|
-
trace_id: data[:trace_id] || data["trace_id"],
|
|
32
|
-
&block
|
|
33
|
-
)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def clear!
|
|
38
|
-
Dex::Trace.clear!
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|