summaryrefslogtreecommitdiffhomepage
path: root/lib/dispatch/tools
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dispatch/tools')
-rw-r--r--lib/dispatch/tools/definition.rb74
-rw-r--r--lib/dispatch/tools/errors.rb15
-rw-r--r--lib/dispatch/tools/interface.rb6
-rw-r--r--lib/dispatch/tools/registry.rb62
-rw-r--r--lib/dispatch/tools/result.rb48
5 files changed, 203 insertions, 2 deletions
diff --git a/lib/dispatch/tools/definition.rb b/lib/dispatch/tools/definition.rb
new file mode 100644
index 0000000..2e386f0
--- /dev/null
+++ b/lib/dispatch/tools/definition.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require "json_schemer"
+
+module Dispatch
+ module Tools
+ class Definition
+ attr_reader :name, :description, :parameters
+
+ def initialize(name:, description:, parameters:, &block)
+ @name = name
+ @description = description
+ @parameters = parameters
+ @block = block
+ @schemer = JSONSchemer.schema(deep_stringify_keys(parameters))
+ end
+
+ def call(params, context: {})
+ symbolized = symbolize_keys(params)
+ valid, errors = validate_params(params)
+
+ unless valid
+ return Result.failure(error: "Parameter validation failed: #{errors.join('; ')}")
+ end
+
+ begin
+ @block.call(symbolized, context)
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ Result.failure(error: "#{e.class}: #{e.message}")
+ end
+ end
+
+ def to_h
+ { name:, description:, parameters: }
+ end
+
+ def to_tool_definition
+ to_h
+ end
+
+ def validate_params(params)
+ stringified = deep_stringify_keys(params)
+ errors = @schemer.validate(stringified).map { |err| err["error"] || err.fetch("type", "unknown error") }
+
+ if errors.empty?
+ [true, []]
+ else
+ [false, errors]
+ end
+ end
+
+ private
+
+ def symbolize_keys(hash)
+ hash.each_with_object({}) do |(key, value), result|
+ result[key.to_sym] = value
+ end
+ end
+
+ def deep_stringify_keys(obj)
+ case obj
+ when Hash
+ obj.each_with_object({}) do |(key, value), result|
+ result[key.to_s] = deep_stringify_keys(value)
+ end
+ when Array
+ obj.map { |item| deep_stringify_keys(item) }
+ else
+ obj
+ end
+ end
+ end
+ end
+end
diff --git a/lib/dispatch/tools/errors.rb b/lib/dispatch/tools/errors.rb
new file mode 100644
index 0000000..c09a529
--- /dev/null
+++ b/lib/dispatch/tools/errors.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Dispatch
+ module Tools
+ class Error < StandardError; end
+
+ class DuplicateToolError < Error; end
+
+ class ToolNotFoundError < Error; end
+
+ class ValidationError < Error; end
+
+ class ExecutionError < Error; end
+ end
+end
diff --git a/lib/dispatch/tools/interface.rb b/lib/dispatch/tools/interface.rb
index 7c88b37..0164941 100644
--- a/lib/dispatch/tools/interface.rb
+++ b/lib/dispatch/tools/interface.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
require_relative "interface/version"
+require_relative "errors"
+require_relative "result"
+require_relative "definition"
+require_relative "registry"
module Dispatch
module Tools
module Interface
- class Error < StandardError; end
- # Your code goes here...
end
end
end
diff --git a/lib/dispatch/tools/registry.rb b/lib/dispatch/tools/registry.rb
new file mode 100644
index 0000000..e4a5d3e
--- /dev/null
+++ b/lib/dispatch/tools/registry.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Dispatch
+ module Tools
+ class Registry
+ def initialize
+ @tools = {}
+ end
+
+ def register(tool_definition)
+ name = tool_definition.name
+
+ if @tools.key?(name)
+ raise DuplicateToolError, "Tool '#{name}' is already registered"
+ end
+
+ @tools[name] = tool_definition
+ self
+ end
+
+ def get(name)
+ @tools.fetch(name) do
+ raise ToolNotFoundError, "Tool '#{name}' not found"
+ end
+ end
+
+ def has?(name)
+ @tools.key?(name)
+ end
+
+ def tools
+ @tools.values
+ end
+
+ def tool_names
+ @tools.keys
+ end
+
+ def to_a
+ @tools.values.map(&:to_tool_definition)
+ end
+
+ def subset(*names)
+ new_registry = self.class.new
+
+ names.each do |name|
+ new_registry.register(get(name))
+ end
+
+ new_registry
+ end
+
+ def size
+ @tools.size
+ end
+
+ def empty?
+ @tools.empty?
+ end
+ end
+ end
+end
diff --git a/lib/dispatch/tools/result.rb b/lib/dispatch/tools/result.rb
new file mode 100644
index 0000000..7e48513
--- /dev/null
+++ b/lib/dispatch/tools/result.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Dispatch
+ module Tools
+ class Result
+ attr_reader :output, :error, :metadata
+
+ def self.success(output:, metadata: {})
+ new(success: true, output:, error: nil, metadata:)
+ end
+
+ def self.failure(error:, metadata: {})
+ new(success: false, output: nil, error:, metadata:)
+ end
+
+ def success?
+ @success
+ end
+
+ def failure?
+ !@success
+ end
+
+ def to_s
+ success? ? output : error
+ end
+
+ def to_h
+ {
+ success: @success,
+ output:,
+ error:,
+ metadata:
+ }
+ end
+
+ private
+
+ def initialize(success:, output:, error:, metadata:)
+ @success = success
+ @output = output
+ @error = error
+ @metadata = metadata
+ freeze
+ end
+ end
+ end
+end