Class: TurnKit::Tool

Inherits:
Object
  • Object
show all
Defined in:
lib/turnkit/tool.rb

Direct Known Subclasses

ImageTool, LoadSkillTool, SubAgentTool, ViewMediaTool

Constant Summary collapse

TYPES =
%i[string integer number boolean array object enum].freeze
NAME_PATTERN =
/\A[a-zA-Z_][a-zA-Z0-9_]*\z/

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.call(arguments = {}, context:) ⇒ Object

Raises:



116
117
118
119
120
121
# File 'lib/turnkit/tool.rb', line 116

def call(arguments = {}, context:)
  required = instance_method(:initialize).parameters.any? { |kind, _| %i[req keyreq].include?(kind) }
  raise ToolError, "#{tool_name} requires constructor arguments; register an instance instead" if required

  invoke(new, arguments, context: context)
end

.completion_message(result) ⇒ Object



57
58
59
60
61
62
63
64
65
66
# File 'lib/turnkit/tool.rb', line 57

def completion_message(result)
  case @completion_message
  when nil
    nil
  when Proc
    @completion_message.call(result)
  else
    @completion_message.to_s
  end
end

.description(value = nil) ⇒ Object



14
15
16
17
# File 'lib/turnkit/tool.rb', line 14

def description(value = nil)
  @description = value.to_s if value
  @description.to_s
end

.ends_turn?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/turnkit/tool.rb', line 53

def ends_turn?
  @ends_turn || false
end

.input_schemaObject



81
82
83
84
85
86
87
88
89
# File 'lib/turnkit/tool.rb', line 81

def input_schema
  properties = parameters.to_h { |param| [ param.fetch(:name), schema_for(param) ] }
  required = parameters.select { |param| param.fetch(:required) }.map { |param| param.fetch(:name) }
  {
    "type" => "object",
    "properties" => properties,
    "required" => required
  }
end

.invoke(instance, arguments = {}, context:) ⇒ Object



123
124
125
126
# File 'lib/turnkit/tool.rb', line 123

def invoke(instance, arguments = {}, context:)
  keyword_arguments = symbolize(validate_arguments(arguments))
  instance.call(**keyword_arguments, context: context)
end

.parameter(name, type = :string, required: false, description: "", default: nil, enum: nil, items: nil, properties: nil) ⇒ Object

Raises:

  • (ArgumentError)


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/turnkit/tool.rb', line 24

def parameter(name, type = :string, required: false, description: "", default: nil, enum: nil, items: nil, properties: nil)
  name = name.to_s
  raise ArgumentError, "unknown parameter type: #{type}" unless TYPES.include?(type)
  raise ArgumentError, "invalid parameter name: #{name}" unless NAME_PATTERN.match?(name)
  raise ArgumentError, "context is a reserved parameter name" if name == "context"
  raise ArgumentError, "duplicate parameter: #{name}" if parameters.any? { |param| param.fetch(:name) == name }
  raise ArgumentError, "enum values are required for enum parameter: #{name}" if type == :enum && Array(enum).empty?

  parameters << {
    name: name,
    type: type,
    required: required ? true : false,
    description: description.to_s,
    default: default,
    enum: enum,
    items: items,
    properties: properties
  }.compact
end

.parametersObject



44
45
46
# File 'lib/turnkit/tool.rb', line 44

def parameters
  @parameters ||= superclass.respond_to?(:parameters) ? superclass.parameters.dup : []
end

.terminal!(message = nil, &block) ⇒ Object



48
49
50
51
# File 'lib/turnkit/tool.rb', line 48

def terminal!(message = nil, &block)
  @ends_turn = true
  @completion_message = block || message
end

.tool_name(value = nil) ⇒ Object



9
10
11
12
# File 'lib/turnkit/tool.rb', line 9

def tool_name(value = nil)
  @tool_name = value.to_s if value
  @tool_name ||= name.to_s.split("::").last.gsub(/([a-z\d])([A-Z])/, "\\1_\\2").downcase
end

.usage_hint(value = nil) ⇒ Object



19
20
21
22
# File 'lib/turnkit/tool.rb', line 19

def usage_hint(value = nil)
  @usage_hint = value.to_s if value
  @usage_hint.to_s
end

.validate_arguments(arguments) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/turnkit/tool.rb', line 91

def validate_arguments(arguments)
  attrs = arguments.respond_to?(:to_h) ? arguments.to_h.transform_keys(&:to_s) : {}
  allowed = parameters.map { |param| param.fetch(:name) }
  unknown = attrs.keys - allowed
  raise ToolValidationError, "unknown argument#{unknown.length == 1 ? "" : "s"}: #{unknown.join(", ")}" if unknown.any?

  normalized = {}
  parameters.each do |param|
    name = param.fetch(:name)
    if attrs.key?(name)
      value = attrs[name]
    elsif param.key?(:default)
      value = param[:default]
    elsif param.fetch(:required)
      raise ToolValidationError, "missing required argument: #{name}"
    else
      next
    end

    SchemaCheck.validate!(value, schema_for(param), error_class: ToolValidationError, label: name)
    normalized[name] = value
  end
  normalized
end

.validate_definition!Object

Raises:

  • (ArgumentError)


68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/turnkit/tool.rb', line 68

def validate_definition!
  raise ArgumentError, "tool name is required" if tool_name.empty?
  raise ArgumentError, "invalid tool name: #{tool_name}" unless NAME_PATTERN.match?(tool_name)

  parameters.each do |param|
    type = param.fetch(:type)
    raise ArgumentError, "unknown parameter type: #{type}" unless TYPES.include?(type)
    raise ArgumentError, "enum values are required for enum parameter: #{param.fetch(:name)}" if type == :enum && Array(param[:enum]).empty?
    validate_value!(param[:default], param) if param.key?(:default)
  end
  true
end

Instance Method Details

#completion_message(result) ⇒ Object



180
# File 'lib/turnkit/tool.rb', line 180

def completion_message(result) = self.class.completion_message(result)

#descriptionObject



174
# File 'lib/turnkit/tool.rb', line 174

def description = self.class.description

#ends_turn?Boolean

Returns:

  • (Boolean)


179
# File 'lib/turnkit/tool.rb', line 179

def ends_turn? = self.class.ends_turn?

#input_schemaObject



177
# File 'lib/turnkit/tool.rb', line 177

def input_schema = self.class.input_schema

#parametersObject



176
# File 'lib/turnkit/tool.rb', line 176

def parameters = self.class.parameters

#tool_nameObject



173
# File 'lib/turnkit/tool.rb', line 173

def tool_name = self.class.tool_name

#usage_hintObject



175
# File 'lib/turnkit/tool.rb', line 175

def usage_hint = self.class.usage_hint

#validate_definition!Object



178
# File 'lib/turnkit/tool.rb', line 178

def validate_definition! = self.class.validate_definition!