From 6b6e158d0614806dc970f7307fb63d392f0cb976 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Tue, 31 Mar 2026 21:36:46 +0900 Subject: imp --- lib/dispatch/tools/definition.rb | 74 ++++++++++++++++++++++++++++++++++++++++ lib/dispatch/tools/errors.rb | 15 ++++++++ lib/dispatch/tools/interface.rb | 6 ++-- lib/dispatch/tools/registry.rb | 62 +++++++++++++++++++++++++++++++++ lib/dispatch/tools/result.rb | 48 ++++++++++++++++++++++++++ 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 lib/dispatch/tools/definition.rb create mode 100644 lib/dispatch/tools/errors.rb create mode 100644 lib/dispatch/tools/registry.rb create mode 100644 lib/dispatch/tools/result.rb (limited to 'lib/dispatch') 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 -- cgit v1.2.3