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.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +259 -0
  4. data/bin/swaig-test +872 -0
  5. data/lib/signalwire/agent/agent_base.rb +2134 -0
  6. data/lib/signalwire/contexts/context_builder.rb +861 -0
  7. data/lib/signalwire/core/logging_config.rb +54 -0
  8. data/lib/signalwire/datamap/data_map.rb +315 -0
  9. data/lib/signalwire/logging.rb +92 -0
  10. data/lib/signalwire/pom/prompt_object_model.rb +269 -0
  11. data/lib/signalwire/pom/section.rb +202 -0
  12. data/lib/signalwire/prefabs/concierge.rb +92 -0
  13. data/lib/signalwire/prefabs/faq_bot.rb +67 -0
  14. data/lib/signalwire/prefabs/info_gatherer.rb +79 -0
  15. data/lib/signalwire/prefabs/receptionist.rb +74 -0
  16. data/lib/signalwire/prefabs/survey.rb +75 -0
  17. data/lib/signalwire/relay/action.rb +291 -0
  18. data/lib/signalwire/relay/call.rb +523 -0
  19. data/lib/signalwire/relay/client.rb +789 -0
  20. data/lib/signalwire/relay/constants.rb +124 -0
  21. data/lib/signalwire/relay/message.rb +137 -0
  22. data/lib/signalwire/relay/relay_event.rb +670 -0
  23. data/lib/signalwire/rest/http_client.rb +159 -0
  24. data/lib/signalwire/rest/namespaces/addresses.rb +19 -0
  25. data/lib/signalwire/rest/namespaces/calling.rb +179 -0
  26. data/lib/signalwire/rest/namespaces/chat.rb +18 -0
  27. data/lib/signalwire/rest/namespaces/compat.rb +229 -0
  28. data/lib/signalwire/rest/namespaces/datasphere.rb +39 -0
  29. data/lib/signalwire/rest/namespaces/fabric.rb +235 -0
  30. data/lib/signalwire/rest/namespaces/imported_numbers.rb +18 -0
  31. data/lib/signalwire/rest/namespaces/logs.rb +46 -0
  32. data/lib/signalwire/rest/namespaces/lookup.rb +18 -0
  33. data/lib/signalwire/rest/namespaces/mfa.rb +26 -0
  34. data/lib/signalwire/rest/namespaces/number_groups.rb +32 -0
  35. data/lib/signalwire/rest/namespaces/phone_numbers.rb +124 -0
  36. data/lib/signalwire/rest/namespaces/project.rb +33 -0
  37. data/lib/signalwire/rest/namespaces/pubsub.rb +18 -0
  38. data/lib/signalwire/rest/namespaces/queues.rb +28 -0
  39. data/lib/signalwire/rest/namespaces/recordings.rb +18 -0
  40. data/lib/signalwire/rest/namespaces/registry.rb +67 -0
  41. data/lib/signalwire/rest/namespaces/short_codes.rb +26 -0
  42. data/lib/signalwire/rest/namespaces/sip_profile.rb +22 -0
  43. data/lib/signalwire/rest/namespaces/verified_callers.rb +24 -0
  44. data/lib/signalwire/rest/namespaces/video.rb +129 -0
  45. data/lib/signalwire/rest/pagination.rb +89 -0
  46. data/lib/signalwire/rest/phone_call_handler.rb +56 -0
  47. data/lib/signalwire/rest/rest_client.rb +114 -0
  48. data/lib/signalwire/runtime.rb +98 -0
  49. data/lib/signalwire/security/session_manager.rb +124 -0
  50. data/lib/signalwire/security/webhook_middleware.rb +191 -0
  51. data/lib/signalwire/security/webhook_validator.rb +327 -0
  52. data/lib/signalwire/server/agent_server.rb +413 -0
  53. data/lib/signalwire/serverless/lambda_handler.rb +251 -0
  54. data/lib/signalwire/skills/builtin/api_ninjas_trivia.rb +99 -0
  55. data/lib/signalwire/skills/builtin/claude_skills.rb +92 -0
  56. data/lib/signalwire/skills/builtin/custom_skills.rb +54 -0
  57. data/lib/signalwire/skills/builtin/datasphere.rb +153 -0
  58. data/lib/signalwire/skills/builtin/datasphere_serverless.rb +107 -0
  59. data/lib/signalwire/skills/builtin/datetime.rb +97 -0
  60. data/lib/signalwire/skills/builtin/google_maps.rb +168 -0
  61. data/lib/signalwire/skills/builtin/info_gatherer.rb +189 -0
  62. data/lib/signalwire/skills/builtin/joke.rb +65 -0
  63. data/lib/signalwire/skills/builtin/math.rb +176 -0
  64. data/lib/signalwire/skills/builtin/mcp_gateway.rb +121 -0
  65. data/lib/signalwire/skills/builtin/native_vector_search.rb +116 -0
  66. data/lib/signalwire/skills/builtin/play_background_file.rb +86 -0
  67. data/lib/signalwire/skills/builtin/spider.rb +169 -0
  68. data/lib/signalwire/skills/builtin/swml_transfer.rb +118 -0
  69. data/lib/signalwire/skills/builtin/weather_api.rb +92 -0
  70. data/lib/signalwire/skills/builtin/web_search.rb +141 -0
  71. data/lib/signalwire/skills/builtin/wikipedia_search.rb +125 -0
  72. data/lib/signalwire/skills/skill_base.rb +82 -0
  73. data/lib/signalwire/skills/skill_manager.rb +97 -0
  74. data/lib/signalwire/skills/skill_registry.rb +258 -0
  75. data/lib/signalwire/swaig/function_result.rb +777 -0
  76. data/lib/signalwire/swml/document.rb +84 -0
  77. data/lib/signalwire/swml/schema.json +12250 -0
  78. data/lib/signalwire/swml/schema.rb +81 -0
  79. data/lib/signalwire/swml/service.rb +650 -0
  80. data/lib/signalwire/utils/schema_utils.rb +298 -0
  81. data/lib/signalwire/utils/serverless.rb +19 -0
  82. data/lib/signalwire/utils/url_validator.rb +138 -0
  83. data/lib/signalwire/version.rb +5 -0
  84. data/lib/signalwire.rb +114 -0
  85. 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