signalwire-sdk 2.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +259 -0
- data/bin/swaig-test +872 -0
- data/lib/signalwire/agent/agent_base.rb +2134 -0
- data/lib/signalwire/contexts/context_builder.rb +861 -0
- data/lib/signalwire/core/logging_config.rb +54 -0
- data/lib/signalwire/datamap/data_map.rb +315 -0
- data/lib/signalwire/logging.rb +92 -0
- data/lib/signalwire/pom/prompt_object_model.rb +269 -0
- data/lib/signalwire/pom/section.rb +202 -0
- data/lib/signalwire/prefabs/concierge.rb +92 -0
- data/lib/signalwire/prefabs/faq_bot.rb +67 -0
- data/lib/signalwire/prefabs/info_gatherer.rb +79 -0
- data/lib/signalwire/prefabs/receptionist.rb +74 -0
- data/lib/signalwire/prefabs/survey.rb +75 -0
- data/lib/signalwire/relay/action.rb +291 -0
- data/lib/signalwire/relay/call.rb +523 -0
- data/lib/signalwire/relay/client.rb +789 -0
- data/lib/signalwire/relay/constants.rb +124 -0
- data/lib/signalwire/relay/message.rb +137 -0
- data/lib/signalwire/relay/relay_event.rb +670 -0
- data/lib/signalwire/rest/http_client.rb +159 -0
- data/lib/signalwire/rest/namespaces/addresses.rb +19 -0
- data/lib/signalwire/rest/namespaces/calling.rb +179 -0
- data/lib/signalwire/rest/namespaces/chat.rb +18 -0
- data/lib/signalwire/rest/namespaces/compat.rb +229 -0
- data/lib/signalwire/rest/namespaces/datasphere.rb +39 -0
- data/lib/signalwire/rest/namespaces/fabric.rb +235 -0
- data/lib/signalwire/rest/namespaces/imported_numbers.rb +18 -0
- data/lib/signalwire/rest/namespaces/logs.rb +46 -0
- data/lib/signalwire/rest/namespaces/lookup.rb +18 -0
- data/lib/signalwire/rest/namespaces/mfa.rb +26 -0
- data/lib/signalwire/rest/namespaces/number_groups.rb +32 -0
- data/lib/signalwire/rest/namespaces/phone_numbers.rb +124 -0
- data/lib/signalwire/rest/namespaces/project.rb +33 -0
- data/lib/signalwire/rest/namespaces/pubsub.rb +18 -0
- data/lib/signalwire/rest/namespaces/queues.rb +28 -0
- data/lib/signalwire/rest/namespaces/recordings.rb +18 -0
- data/lib/signalwire/rest/namespaces/registry.rb +67 -0
- data/lib/signalwire/rest/namespaces/short_codes.rb +26 -0
- data/lib/signalwire/rest/namespaces/sip_profile.rb +22 -0
- data/lib/signalwire/rest/namespaces/verified_callers.rb +24 -0
- data/lib/signalwire/rest/namespaces/video.rb +129 -0
- data/lib/signalwire/rest/pagination.rb +89 -0
- data/lib/signalwire/rest/phone_call_handler.rb +56 -0
- data/lib/signalwire/rest/rest_client.rb +114 -0
- data/lib/signalwire/runtime.rb +98 -0
- data/lib/signalwire/security/session_manager.rb +124 -0
- data/lib/signalwire/security/webhook_middleware.rb +191 -0
- data/lib/signalwire/security/webhook_validator.rb +327 -0
- data/lib/signalwire/server/agent_server.rb +413 -0
- data/lib/signalwire/serverless/lambda_handler.rb +251 -0
- data/lib/signalwire/skills/builtin/api_ninjas_trivia.rb +99 -0
- data/lib/signalwire/skills/builtin/claude_skills.rb +92 -0
- data/lib/signalwire/skills/builtin/custom_skills.rb +54 -0
- data/lib/signalwire/skills/builtin/datasphere.rb +153 -0
- data/lib/signalwire/skills/builtin/datasphere_serverless.rb +107 -0
- data/lib/signalwire/skills/builtin/datetime.rb +97 -0
- data/lib/signalwire/skills/builtin/google_maps.rb +168 -0
- data/lib/signalwire/skills/builtin/info_gatherer.rb +189 -0
- data/lib/signalwire/skills/builtin/joke.rb +65 -0
- data/lib/signalwire/skills/builtin/math.rb +176 -0
- data/lib/signalwire/skills/builtin/mcp_gateway.rb +121 -0
- data/lib/signalwire/skills/builtin/native_vector_search.rb +116 -0
- data/lib/signalwire/skills/builtin/play_background_file.rb +86 -0
- data/lib/signalwire/skills/builtin/spider.rb +169 -0
- data/lib/signalwire/skills/builtin/swml_transfer.rb +118 -0
- data/lib/signalwire/skills/builtin/weather_api.rb +92 -0
- data/lib/signalwire/skills/builtin/web_search.rb +141 -0
- data/lib/signalwire/skills/builtin/wikipedia_search.rb +125 -0
- data/lib/signalwire/skills/skill_base.rb +82 -0
- data/lib/signalwire/skills/skill_manager.rb +97 -0
- data/lib/signalwire/skills/skill_registry.rb +258 -0
- data/lib/signalwire/swaig/function_result.rb +777 -0
- data/lib/signalwire/swml/document.rb +84 -0
- data/lib/signalwire/swml/schema.json +12250 -0
- data/lib/signalwire/swml/schema.rb +81 -0
- data/lib/signalwire/swml/service.rb +650 -0
- data/lib/signalwire/utils/schema_utils.rb +298 -0
- data/lib/signalwire/utils/serverless.rb +19 -0
- data/lib/signalwire/utils/url_validator.rb +138 -0
- data/lib/signalwire/version.rb +5 -0
- data/lib/signalwire.rb +114 -0
- metadata +225 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2025 SignalWire
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the MIT License.
|
|
6
|
+
# See LICENSE file in the project root for full license information.
|
|
7
|
+
|
|
8
|
+
require 'thread'
|
|
9
|
+
require_relative '../logging'
|
|
10
|
+
|
|
11
|
+
module SignalWire
|
|
12
|
+
module Skills
|
|
13
|
+
# Global registry mapping skill names to factory lambdas.
|
|
14
|
+
#
|
|
15
|
+
# SkillRegistry.register('datetime') { |params| DateTimeSkill.new(params) }
|
|
16
|
+
# factory = SkillRegistry.get_factory('datetime')
|
|
17
|
+
# skill = factory.call({ 'timezone' => 'UTC' })
|
|
18
|
+
#
|
|
19
|
+
class SkillRegistry
|
|
20
|
+
@factories = {} # skill_name => lambda { |params| SkillBase }
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
|
|
23
|
+
# Per-instance state for the skill-directory parity surface; the
|
|
24
|
+
# class-method API above is preserved for backwards compatibility,
|
|
25
|
+
# but `add_skill_directory` mirrors Python's instance-method shape
|
|
26
|
+
# exactly (Python's `signalwire.skills.registry.SkillRegistry`).
|
|
27
|
+
def initialize
|
|
28
|
+
@external_paths = []
|
|
29
|
+
@inst_mutex = Mutex.new
|
|
30
|
+
@logger = ::SignalWire::Logging.logger('skill_registry')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Python parity: ``self.logger = get_logger("skill_registry")``.
|
|
34
|
+
# Per-instance logger; the class-level API uses the same name.
|
|
35
|
+
attr_reader :logger
|
|
36
|
+
|
|
37
|
+
# External skill directories registered via #add_skill_directory.
|
|
38
|
+
# Mirrors Python's `_external_paths` accessor surface.
|
|
39
|
+
attr_reader :external_paths
|
|
40
|
+
|
|
41
|
+
# Add a directory to search for skills.
|
|
42
|
+
#
|
|
43
|
+
# Mirrors Python's `SkillRegistry.add_skill_directory`: validate
|
|
44
|
+
# that the path exists and is a directory, then append it
|
|
45
|
+
# (de-duplicated) to `@external_paths`. Raises `ArgumentError`
|
|
46
|
+
# (the Ruby analog of Python's `ValueError`) for invalid input.
|
|
47
|
+
#
|
|
48
|
+
# @param path [String] absolute or relative path to a directory
|
|
49
|
+
# @return [void]
|
|
50
|
+
# @raise [ArgumentError] when the path doesn't exist or isn't a
|
|
51
|
+
# directory.
|
|
52
|
+
def add_skill_directory(path)
|
|
53
|
+
@inst_mutex.synchronize do
|
|
54
|
+
unless File.exist?(path)
|
|
55
|
+
raise ArgumentError, "Skill directory does not exist: #{path}"
|
|
56
|
+
end
|
|
57
|
+
unless File.directory?(path)
|
|
58
|
+
raise ArgumentError, "Path is not a directory: #{path}"
|
|
59
|
+
end
|
|
60
|
+
@external_paths << path unless @external_paths.include?(path)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get complete schema for all registered skills (instance form).
|
|
65
|
+
#
|
|
66
|
+
# Mirrors Python's instance-method
|
|
67
|
+
# ``SkillRegistry.get_all_skills_schema()`` — returns a hash keyed
|
|
68
|
+
# by skill name, each value containing parameter metadata. Ruby
|
|
69
|
+
# skills don't carry rich Python-style parameter introspection in
|
|
70
|
+
# v1, so the value defaults to a minimal shape with the skill
|
|
71
|
+
# name; built-ins that expose ``parameter_schema`` get richer
|
|
72
|
+
# detail.
|
|
73
|
+
#
|
|
74
|
+
# @return [Hash{String => Hash}]
|
|
75
|
+
def get_all_skills_schema
|
|
76
|
+
self.class.get_all_skills_schema
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# List all registered skill names (instance form).
|
|
80
|
+
#
|
|
81
|
+
# Python parity: ``SkillRegistry.list_skills(self)`` returns a list of
|
|
82
|
+
# dictionaries describing each skill. Ruby v1 returns the
|
|
83
|
+
# registered names plus available metadata (description / version)
|
|
84
|
+
# when the factory can be instantiated without arguments.
|
|
85
|
+
#
|
|
86
|
+
# @return [Array<Hash>]
|
|
87
|
+
def list_skills
|
|
88
|
+
self.class.send(:_list_skills_full)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Register a skill class or factory (instance form).
|
|
92
|
+
#
|
|
93
|
+
# Python parity: ``SkillRegistry.register_skill(self, skill_class)``
|
|
94
|
+
# accepts a SkillBase subclass and stores its factory. Ruby
|
|
95
|
+
# accepts either a class with a ``new(params)`` constructor, a
|
|
96
|
+
# ``Proc`` /``Lambda``, or a 2-arg ``(name, factory)`` form for
|
|
97
|
+
# explicit naming. Returns ``self`` for chaining.
|
|
98
|
+
#
|
|
99
|
+
# @param skill_class_or_name [Class, String] either a SkillBase
|
|
100
|
+
# subclass (Python style) or a string skill name (legacy
|
|
101
|
+
# 2-arg form).
|
|
102
|
+
# @param factory [Proc, nil] explicit factory when first arg
|
|
103
|
+
# is a string (legacy form).
|
|
104
|
+
def register_skill(skill_class_or_name, factory = nil)
|
|
105
|
+
if skill_class_or_name.is_a?(String)
|
|
106
|
+
# Legacy 2-arg form: register_skill(name, factory)
|
|
107
|
+
self.class.register_skill(skill_class_or_name, factory)
|
|
108
|
+
return self
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
skill_class = skill_class_or_name
|
|
112
|
+
unless skill_class.respond_to?(:new)
|
|
113
|
+
raise ArgumentError,
|
|
114
|
+
"register_skill expects a class with .new or a (name, factory) pair"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Pull the skill name from a class-level method or constant.
|
|
118
|
+
name = if skill_class.respond_to?(:skill_name)
|
|
119
|
+
skill_class.skill_name
|
|
120
|
+
elsif skill_class.const_defined?(:SKILL_NAME)
|
|
121
|
+
skill_class.const_get(:SKILL_NAME)
|
|
122
|
+
else
|
|
123
|
+
# Try instantiating with no args to read .name from the
|
|
124
|
+
# instance — Ruby idiom for skills that lack a
|
|
125
|
+
# class-level constant.
|
|
126
|
+
begin
|
|
127
|
+
skill_class.new.name
|
|
128
|
+
rescue StandardError
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
raise ArgumentError, "Cannot determine skill name for #{skill_class}" if name.nil?
|
|
134
|
+
|
|
135
|
+
self.class.register_skill(name.to_s, ->(params = {}) { skill_class.new(params) })
|
|
136
|
+
@inst_mutex.synchronize { @last_registered = name.to_s }
|
|
137
|
+
self
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The most recently registered skill name (instance form).
|
|
141
|
+
attr_reader :last_registered
|
|
142
|
+
|
|
143
|
+
class << self
|
|
144
|
+
# Register a skill factory.
|
|
145
|
+
# @param skill_name [String]
|
|
146
|
+
# @yield [params] block that receives params hash and returns a SkillBase
|
|
147
|
+
def register(skill_name, &block)
|
|
148
|
+
@mutex.synchronize do
|
|
149
|
+
@factories[skill_name.to_s] = block
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Register with an explicit lambda / proc instead of a block.
|
|
154
|
+
# @param skill_name [String]
|
|
155
|
+
# @param factory [Proc]
|
|
156
|
+
def register_skill(skill_name, factory)
|
|
157
|
+
@mutex.synchronize do
|
|
158
|
+
@factories[skill_name.to_s] = factory
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get the factory for a skill.
|
|
163
|
+
# @param skill_name [String]
|
|
164
|
+
# @return [Proc, nil]
|
|
165
|
+
def get_factory(skill_name)
|
|
166
|
+
@mutex.synchronize { @factories[skill_name.to_s] }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# List all registered skill names.
|
|
170
|
+
# @return [Array<String>]
|
|
171
|
+
def list_skills
|
|
172
|
+
@mutex.synchronize { @factories.keys.dup }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Full skill metadata (Python instance-method parity for
|
|
176
|
+
# SkillRegistry.list_skills). Returns one dict per skill with
|
|
177
|
+
# name + description + version when available.
|
|
178
|
+
# @api private
|
|
179
|
+
def _list_skills_full
|
|
180
|
+
@mutex.synchronize do
|
|
181
|
+
@factories.keys.sort.map do |skill_name|
|
|
182
|
+
entry = { 'name' => skill_name }
|
|
183
|
+
factory = @factories[skill_name]
|
|
184
|
+
if factory.respond_to?(:call)
|
|
185
|
+
begin
|
|
186
|
+
instance = factory.call({})
|
|
187
|
+
entry['description'] = instance.description if instance.respond_to?(:description)
|
|
188
|
+
entry['version'] = instance.version if instance.respond_to?(:version)
|
|
189
|
+
rescue StandardError
|
|
190
|
+
# Skill needs constructor args; fall back to name-only.
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
entry
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Check if a skill is registered.
|
|
199
|
+
# @param skill_name [String]
|
|
200
|
+
# @return [Boolean]
|
|
201
|
+
def registered?(skill_name)
|
|
202
|
+
@mutex.synchronize { @factories.key?(skill_name.to_s) }
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Clear all registrations (primarily for testing).
|
|
206
|
+
def reset!
|
|
207
|
+
@mutex.synchronize { @factories.clear }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Register all built-in skills. Called at load time.
|
|
211
|
+
def register_builtins!
|
|
212
|
+
# Each builtin file calls SkillRegistry.register on require.
|
|
213
|
+
# We just need to require them all.
|
|
214
|
+
builtin_dir = File.join(__dir__, 'builtin')
|
|
215
|
+
Dir[File.join(builtin_dir, '*.rb')].sort.each { |f| require f }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Get complete schema for all registered skills.
|
|
219
|
+
#
|
|
220
|
+
# Mirrors Python's
|
|
221
|
+
# ``SkillRegistry.get_all_skills_schema()`` — returns a hash
|
|
222
|
+
# keyed by skill name, with each value containing parameter
|
|
223
|
+
# metadata. Ruby skills don't carry rich Python-style parameter
|
|
224
|
+
# introspection in v1, so the value defaults to a minimal shape
|
|
225
|
+
# with the skill name; built-in skills that expose
|
|
226
|
+
# ``parameter_schema`` get richer detail.
|
|
227
|
+
#
|
|
228
|
+
# @return [Hash{String => Hash}]
|
|
229
|
+
def get_all_skills_schema
|
|
230
|
+
@mutex.synchronize do
|
|
231
|
+
@factories.keys.sort.each_with_object({}) do |name, h|
|
|
232
|
+
entry = { 'name' => name, 'parameters' => {} }
|
|
233
|
+
factory = @factories[name]
|
|
234
|
+
if factory.respond_to?(:call)
|
|
235
|
+
begin
|
|
236
|
+
instance = factory.call({})
|
|
237
|
+
if instance.respond_to?(:parameter_schema)
|
|
238
|
+
entry['parameters'] = instance.parameter_schema || {}
|
|
239
|
+
end
|
|
240
|
+
if instance.class.respond_to?(:skill_description)
|
|
241
|
+
entry['description'] = instance.class.skill_description
|
|
242
|
+
end
|
|
243
|
+
if instance.class.respond_to?(:skill_version)
|
|
244
|
+
entry['version'] = instance.class.skill_version
|
|
245
|
+
end
|
|
246
|
+
rescue StandardError
|
|
247
|
+
# If we can't instantiate without params, fall back to
|
|
248
|
+
# the minimal entry.
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
h[name] = entry
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|