Class: SignalWire::Skills::SkillRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/signalwire/skills/skill_registry.rb

Overview

Global registry mapping skill names to factory lambdas.

SkillRegistry.register('datetime') { |params| DateTimeSkill.new(params) }
factory = SkillRegistry.get_factory('datetime')
skill   = factory.call({ 'timezone' => 'UTC' })

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSkillRegistry

Per-instance state for the skill-directory parity surface; the class-method API above is preserved for backwards compatibility, but ‘add_skill_directory` mirrors Python’s instance-method shape exactly (Python’s ‘signalwire.skills.registry.SkillRegistry`).



27
28
29
30
31
# File 'lib/signalwire/skills/skill_registry.rb', line 27

def initialize
  @external_paths = []
  @inst_mutex     = Mutex.new
  @logger         = ::SignalWire::Logging.logger('skill_registry')
end

Instance Attribute Details

#external_pathsObject (readonly)

External skill directories registered via #add_skill_directory. Mirrors Python’s ‘_external_paths` accessor surface.



39
40
41
# File 'lib/signalwire/skills/skill_registry.rb', line 39

def external_paths
  @external_paths
end

#last_registeredObject (readonly)

The most recently registered skill name (instance form).



141
142
143
# File 'lib/signalwire/skills/skill_registry.rb', line 141

def last_registered
  @last_registered
end

#loggerObject (readonly)

Python parity: “self.logger = get_logger(“skill_registry”)“. Per-instance logger; the class-level API uses the same name.



35
36
37
# File 'lib/signalwire/skills/skill_registry.rb', line 35

def logger
  @logger
end

Class Method Details

._list_skills_fullObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Full skill metadata (Python instance-method parity for SkillRegistry.list_skills). Returns one dict per skill with name + description + version when available.



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/signalwire/skills/skill_registry.rb', line 179

def _list_skills_full
  @mutex.synchronize do
    @factories.keys.sort.map do |skill_name|
      entry = { 'name' => skill_name }
      factory = @factories[skill_name]
      if factory.respond_to?(:call)
        begin
          instance = factory.call({})
          entry['description'] = instance.description if instance.respond_to?(:description)
          entry['version']     = instance.version     if instance.respond_to?(:version)
        rescue StandardError
          # Skill needs constructor args; fall back to name-only.
        end
      end
      entry
    end
  end
end

.get_all_skills_schemaHash{String => Hash}

Get complete schema for all registered skills.

Mirrors Python’s “SkillRegistry.get_all_skills_schema()“ — returns a hash keyed by skill name, with each value containing parameter metadata. Ruby skills don’t carry rich Python-style parameter introspection in v1, so the value defaults to a minimal shape with the skill name; built-in skills that expose “parameter_schema“ get richer detail.

Returns:

  • (Hash{String => Hash})


229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/signalwire/skills/skill_registry.rb', line 229

def get_all_skills_schema
  @mutex.synchronize do
    @factories.keys.sort.each_with_object({}) do |name, h|
      entry = { 'name' => name, 'parameters' => {} }
      factory = @factories[name]
      if factory.respond_to?(:call)
        begin
          instance = factory.call({})
          if instance.respond_to?(:parameter_schema)
            entry['parameters'] = instance.parameter_schema || {}
          end
          if instance.class.respond_to?(:skill_description)
            entry['description'] = instance.class.skill_description
          end
          if instance.class.respond_to?(:skill_version)
            entry['version'] = instance.class.skill_version
          end
        rescue StandardError
          # If we can't instantiate without params, fall back to
          # the minimal entry.
        end
      end
      h[name] = entry
    end
  end
end

.get_factory(skill_name) ⇒ Proc?

Get the factory for a skill.

Parameters:

  • skill_name (String)

Returns:

  • (Proc, nil)


165
166
167
# File 'lib/signalwire/skills/skill_registry.rb', line 165

def get_factory(skill_name)
  @mutex.synchronize { @factories[skill_name.to_s] }
end

.list_skillsArray<String>

List all registered skill names.

Returns:

  • (Array<String>)


171
172
173
# File 'lib/signalwire/skills/skill_registry.rb', line 171

def list_skills
  @mutex.synchronize { @factories.keys.dup }
end

.register(skill_name) {|params| ... } ⇒ Object

Register a skill factory.

Parameters:

  • skill_name (String)

Yields:

  • (params)

    block that receives params hash and returns a SkillBase



147
148
149
150
151
# File 'lib/signalwire/skills/skill_registry.rb', line 147

def register(skill_name, &block)
  @mutex.synchronize do
    @factories[skill_name.to_s] = block
  end
end

.register_builtins!Object

Register all built-in skills. Called at load time.



211
212
213
214
215
216
# File 'lib/signalwire/skills/skill_registry.rb', line 211

def register_builtins!
  # Each builtin file calls SkillRegistry.register on require.
  # We just need to require them all.
  builtin_dir = File.join(__dir__, 'builtin')
  Dir[File.join(builtin_dir, '*.rb')].sort.each { |f| require f }
end

.register_skill(skill_name, factory) ⇒ Object

Register with an explicit lambda / proc instead of a block.

Parameters:

  • skill_name (String)
  • factory (Proc)


156
157
158
159
160
# File 'lib/signalwire/skills/skill_registry.rb', line 156

def register_skill(skill_name, factory)
  @mutex.synchronize do
    @factories[skill_name.to_s] = factory
  end
end

.registered?(skill_name) ⇒ Boolean

Check if a skill is registered.

Parameters:

  • skill_name (String)

Returns:

  • (Boolean)


201
202
203
# File 'lib/signalwire/skills/skill_registry.rb', line 201

def registered?(skill_name)
  @mutex.synchronize { @factories.key?(skill_name.to_s) }
end

.reset!Object

Clear all registrations (primarily for testing).



206
207
208
# File 'lib/signalwire/skills/skill_registry.rb', line 206

def reset!
  @mutex.synchronize { @factories.clear }
end

Instance Method Details

#add_skill_directory(path) ⇒ void

This method returns an undefined value.

Add a directory to search for skills.

Mirrors Python’s ‘SkillRegistry.add_skill_directory`: validate that the path exists and is a directory, then append it (de-duplicated) to `@external_paths`. Raises `ArgumentError` (the Ruby analog of Python’s ‘ValueError`) for invalid input.

Parameters:

  • path (String)

    absolute or relative path to a directory

Raises:

  • (ArgumentError)

    when the path doesn’t exist or isn’t a directory.



52
53
54
55
56
57
58
59
60
61
62
# File 'lib/signalwire/skills/skill_registry.rb', line 52

def add_skill_directory(path)
  @inst_mutex.synchronize do
    unless File.exist?(path)
      raise ArgumentError, "Skill directory does not exist: #{path}"
    end
    unless File.directory?(path)
      raise ArgumentError, "Path is not a directory: #{path}"
    end
    @external_paths << path unless @external_paths.include?(path)
  end
end

#get_all_skills_schemaHash{String => Hash}

Get complete schema for all registered skills (instance form).

Mirrors Python’s instance-method “SkillRegistry.get_all_skills_schema()“ — returns a hash keyed by skill name, each value containing parameter metadata. Ruby skills don’t carry rich Python-style parameter introspection in v1, so the value defaults to a minimal shape with the skill name; built-ins that expose “parameter_schema“ get richer detail.

Returns:

  • (Hash{String => Hash})


75
76
77
# File 'lib/signalwire/skills/skill_registry.rb', line 75

def get_all_skills_schema
  self.class.get_all_skills_schema
end

#list_skillsArray<Hash>

List all registered skill names (instance form).

Python parity: “SkillRegistry.list_skills(self)“ returns a list of dictionaries describing each skill. Ruby v1 returns the registered names plus available metadata (description / version) when the factory can be instantiated without arguments.

Returns:

  • (Array<Hash>)


87
88
89
# File 'lib/signalwire/skills/skill_registry.rb', line 87

def list_skills
  self.class.send(:_list_skills_full)
end

#register_skill(skill_class_or_name, factory = nil) ⇒ Object

Register a skill class or factory (instance form).

Python parity: “SkillRegistry.register_skill(self, skill_class)“ accepts a SkillBase subclass and stores its factory. Ruby accepts either a class with a “new(params)“ constructor, a “Proc“ /“Lambda“, or a 2-arg “(name, factory)“ form for explicit naming. Returns “self“ for chaining.

Parameters:

  • skill_class_or_name (Class, String)

    either a SkillBase subclass (Python style) or a string skill name (legacy 2-arg form).

  • factory (Proc, nil) (defaults to: nil)

    explicit factory when first arg is a string (legacy form).

Raises:

  • (ArgumentError)


104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/signalwire/skills/skill_registry.rb', line 104

def register_skill(skill_class_or_name, factory = nil)
  if skill_class_or_name.is_a?(String)
    # Legacy 2-arg form: register_skill(name, factory)
    self.class.register_skill(skill_class_or_name, factory)
    return self
  end

  skill_class = skill_class_or_name
  unless skill_class.respond_to?(:new)
    raise ArgumentError,
          "register_skill expects a class with .new or a (name, factory) pair"
  end

  # Pull the skill name from a class-level method or constant.
  name = if skill_class.respond_to?(:skill_name)
           skill_class.skill_name
         elsif skill_class.const_defined?(:SKILL_NAME)
           skill_class.const_get(:SKILL_NAME)
         else
           # Try instantiating with no args to read .name from the
           # instance — Ruby idiom for skills that lack a
           # class-level constant.
           begin
             skill_class.new.name
           rescue StandardError
             nil
           end
         end

  raise ArgumentError, "Cannot determine skill name for #{skill_class}" if name.nil?

  self.class.register_skill(name.to_s, ->(params = {}) { skill_class.new(params) })
  @inst_mutex.synchronize { @last_registered = name.to_s }
  self
end