signalwire_agents 1.1.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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +79 -0
  3. data/bin/swaig-test +310 -0
  4. data/lib/signalwire_agents/agent/agent_base.rb +1171 -0
  5. data/lib/signalwire_agents/contexts/context_builder.rb +622 -0
  6. data/lib/signalwire_agents/datamap/data_map.rb +279 -0
  7. data/lib/signalwire_agents/logging.rb +92 -0
  8. data/lib/signalwire_agents/prefabs/concierge.rb +92 -0
  9. data/lib/signalwire_agents/prefabs/faq_bot.rb +67 -0
  10. data/lib/signalwire_agents/prefabs/info_gatherer.rb +79 -0
  11. data/lib/signalwire_agents/prefabs/receptionist.rb +74 -0
  12. data/lib/signalwire_agents/prefabs/survey.rb +75 -0
  13. data/lib/signalwire_agents/relay/action.rb +291 -0
  14. data/lib/signalwire_agents/relay/call.rb +523 -0
  15. data/lib/signalwire_agents/relay/client.rb +671 -0
  16. data/lib/signalwire_agents/relay/constants.rb +124 -0
  17. data/lib/signalwire_agents/relay/message.rb +137 -0
  18. data/lib/signalwire_agents/relay/relay_event.rb +670 -0
  19. data/lib/signalwire_agents/rest/http_client.rb +147 -0
  20. data/lib/signalwire_agents/rest/namespaces/addresses.rb +19 -0
  21. data/lib/signalwire_agents/rest/namespaces/calling.rb +179 -0
  22. data/lib/signalwire_agents/rest/namespaces/chat.rb +18 -0
  23. data/lib/signalwire_agents/rest/namespaces/compat.rb +229 -0
  24. data/lib/signalwire_agents/rest/namespaces/datasphere.rb +39 -0
  25. data/lib/signalwire_agents/rest/namespaces/fabric.rb +175 -0
  26. data/lib/signalwire_agents/rest/namespaces/imported_numbers.rb +18 -0
  27. data/lib/signalwire_agents/rest/namespaces/logs.rb +46 -0
  28. data/lib/signalwire_agents/rest/namespaces/lookup.rb +18 -0
  29. data/lib/signalwire_agents/rest/namespaces/mfa.rb +26 -0
  30. data/lib/signalwire_agents/rest/namespaces/number_groups.rb +32 -0
  31. data/lib/signalwire_agents/rest/namespaces/phone_numbers.rb +20 -0
  32. data/lib/signalwire_agents/rest/namespaces/project.rb +33 -0
  33. data/lib/signalwire_agents/rest/namespaces/pubsub.rb +18 -0
  34. data/lib/signalwire_agents/rest/namespaces/queues.rb +28 -0
  35. data/lib/signalwire_agents/rest/namespaces/recordings.rb +18 -0
  36. data/lib/signalwire_agents/rest/namespaces/registry.rb +67 -0
  37. data/lib/signalwire_agents/rest/namespaces/short_codes.rb +26 -0
  38. data/lib/signalwire_agents/rest/namespaces/sip_profile.rb +22 -0
  39. data/lib/signalwire_agents/rest/namespaces/verified_callers.rb +24 -0
  40. data/lib/signalwire_agents/rest/namespaces/video.rb +129 -0
  41. data/lib/signalwire_agents/rest/signalwire_client.rb +110 -0
  42. data/lib/signalwire_agents/security/session_manager.rb +124 -0
  43. data/lib/signalwire_agents/server/agent_server.rb +260 -0
  44. data/lib/signalwire_agents/skills/builtin/api_ninjas_trivia.rb +91 -0
  45. data/lib/signalwire_agents/skills/builtin/claude_skills.rb +92 -0
  46. data/lib/signalwire_agents/skills/builtin/custom_skills.rb +54 -0
  47. data/lib/signalwire_agents/skills/builtin/datasphere.rb +141 -0
  48. data/lib/signalwire_agents/skills/builtin/datasphere_serverless.rb +107 -0
  49. data/lib/signalwire_agents/skills/builtin/datetime.rb +97 -0
  50. data/lib/signalwire_agents/skills/builtin/google_maps.rb +168 -0
  51. data/lib/signalwire_agents/skills/builtin/info_gatherer.rb +189 -0
  52. data/lib/signalwire_agents/skills/builtin/joke.rb +65 -0
  53. data/lib/signalwire_agents/skills/builtin/math.rb +176 -0
  54. data/lib/signalwire_agents/skills/builtin/mcp_gateway.rb +121 -0
  55. data/lib/signalwire_agents/skills/builtin/native_vector_search.rb +116 -0
  56. data/lib/signalwire_agents/skills/builtin/play_background_file.rb +86 -0
  57. data/lib/signalwire_agents/skills/builtin/spider.rb +142 -0
  58. data/lib/signalwire_agents/skills/builtin/swml_transfer.rb +118 -0
  59. data/lib/signalwire_agents/skills/builtin/weather_api.rb +85 -0
  60. data/lib/signalwire_agents/skills/builtin/web_search.rb +123 -0
  61. data/lib/signalwire_agents/skills/builtin/wikipedia_search.rb +109 -0
  62. data/lib/signalwire_agents/skills/skill_base.rb +58 -0
  63. data/lib/signalwire_agents/skills/skill_manager.rb +85 -0
  64. data/lib/signalwire_agents/skills/skill_registry.rb +76 -0
  65. data/lib/signalwire_agents/swaig/function_result.rb +777 -0
  66. data/lib/signalwire_agents/swml/document.rb +84 -0
  67. data/lib/signalwire_agents/swml/schema.json +12250 -0
  68. data/lib/signalwire_agents/swml/schema.rb +81 -0
  69. data/lib/signalwire_agents/swml/service.rb +304 -0
  70. data/lib/signalwire_agents/version.rb +5 -0
  71. data/lib/signalwire_agents.rb +19 -0
  72. metadata +212 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8d554ac59cc617d87de3cf7df045edc032d3ab8f69d09d5598f4dd5dc374d4f5
4
+ data.tar.gz: 5201aa822268884652d5fcdd89068439c8ab197d3962018c1582e1f1b73148ff
5
+ SHA512:
6
+ metadata.gz: 280445542c8ce446aa5d0860d03b986f217daf3dbdce2730f340d08eab6eb5a16840fab8943c8b48b8876ac06f5c472364240eafd4a6b2c52c6d52ec66710797
7
+ data.tar.gz: 3a7b8170897cd823d4cbbfa44a584d663f4aab56d5c23c115d09071f63a83ca1e7ada025208f0be35cd0b5829ed224ec9943e2fd3bd7e09537933c7d122d2361
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # SignalWire AI Agents Ruby SDK
2
+
3
+ A Ruby framework for building, deploying, and managing AI agents as microservices that interact with the [SignalWire](https://signalwire.com) platform.
4
+
5
+ ## Features
6
+
7
+ - **Agent Framework** — Build AI agents with structured prompts, tools, and skills
8
+ - **SWML Generation** — Automatic SWML document creation for the SignalWire AI platform
9
+ - **SWAIG Functions** — Define tools the AI can call during conversations
10
+ - **DataMap Tools** — Server-side API integrations without webhook infrastructure
11
+ - **Contexts & Steps** — Structured multi-step conversation workflows
12
+ - **Skills System** — Modular, reusable capabilities (datetime, math, web search, etc.)
13
+ - **Prefab Agents** — Ready-to-use agent patterns (surveys, reception, FAQ, etc.)
14
+ - **Multi-Agent Hosting** — Run multiple agents on a single server
15
+ - **RELAY Client** — Real-time WebSocket-based call control and messaging
16
+ - **REST Client** — Full SignalWire REST API access with typed resources
17
+ - **Rack Compatible** — Run standalone or mount in Rails, Sinatra, or any Rack app
18
+
19
+ ## Quick Start
20
+
21
+ ```ruby
22
+ require 'signalwire_agents'
23
+
24
+ agent = SignalWireAgents::AgentBase.new(name: 'my-agent')
25
+
26
+ agent.set_prompt_text("You are a helpful assistant.")
27
+
28
+ agent.define_tool(
29
+ name: 'get_time',
30
+ description: 'Get the current time',
31
+ parameters: {}
32
+ ) do |args, raw_data|
33
+ SignalWireAgents::FunctionResult.new("The current time is #{Time.now}")
34
+ end
35
+
36
+ agent.run
37
+ ```
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ gem install signalwire_agents
43
+ ```
44
+
45
+ Or in your Gemfile:
46
+
47
+ ```ruby
48
+ gem 'signalwire_agents'
49
+ ```
50
+
51
+ ## Rack / Rails Integration
52
+
53
+ ```ruby
54
+ # config/routes.rb (Rails)
55
+ mount MyAgent.rack_app => '/agent'
56
+
57
+ # config.ru (Rack)
58
+ run MyAgent.rack_app
59
+ ```
60
+
61
+ ## Documentation
62
+
63
+ See the [docs/](docs/) directory for comprehensive guides.
64
+
65
+ ## Environment Variables
66
+
67
+ | Variable | Description | Default |
68
+ |----------|-------------|---------|
69
+ | `PORT` | HTTP server port | `3000` |
70
+ | `SWML_BASIC_AUTH_USER` | Basic auth username | auto-generated |
71
+ | `SWML_BASIC_AUTH_PASSWORD` | Basic auth password | auto-generated |
72
+ | `SWML_PROXY_URL_BASE` | Proxy/tunnel base URL | auto-detected |
73
+ | `SIGNALWIRE_PROJECT_ID` | Project ID for RELAY/REST | — |
74
+ | `SIGNALWIRE_API_TOKEN` | API token for RELAY/REST | — |
75
+ | `SIGNALWIRE_SPACE` | Space hostname | — |
76
+
77
+ ## License
78
+
79
+ Copyright (c) SignalWire. All rights reserved.
data/bin/swaig-test ADDED
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright (c) 2025 SignalWire
5
+ #
6
+ # Licensed under the MIT License.
7
+ # See LICENSE file in the project root for full license information.
8
+
9
+ #
10
+ # swaig-test — CLI tool for testing SWAIG agent endpoints.
11
+ #
12
+ # Usage:
13
+ # swaig-test --url http://user:pass@host:port/route --dump-swml
14
+ # swaig-test --url http://user:pass@host:port/route --list-tools
15
+ # swaig-test --url http://user:pass@host:port/route --exec tool_name --param key=value
16
+ #
17
+
18
+ require 'net/http'
19
+ require 'uri'
20
+ require 'json'
21
+ require 'optparse'
22
+
23
+ module SwaigTest
24
+ class CLI
25
+ attr_reader :options
26
+
27
+ def initialize(argv = ARGV)
28
+ @options = {
29
+ url: nil,
30
+ dump_swml: false,
31
+ list_tools: false,
32
+ exec: nil,
33
+ params: {},
34
+ raw: false,
35
+ verbose: false
36
+ }
37
+ parse_options(argv)
38
+ end
39
+
40
+ def run
41
+ validate_options!
42
+
43
+ if @options[:dump_swml]
44
+ dump_swml
45
+ elsif @options[:list_tools]
46
+ list_tools
47
+ elsif @options[:exec]
48
+ exec_function
49
+ else
50
+ $stderr.puts "Error: specify --dump-swml, --list-tools, or --exec NAME"
51
+ exit 1
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def parse_options(argv)
58
+ parser = OptionParser.new do |opts|
59
+ opts.banner = "Usage: swaig-test [options]"
60
+
61
+ opts.on("--url URL", "Agent URL with embedded auth (http://user:pass@host:port/route)") do |v|
62
+ @options[:url] = v
63
+ end
64
+
65
+ opts.on("--dump-swml", "GET SWML document and pretty-print it") do
66
+ @options[:dump_swml] = true
67
+ end
68
+
69
+ opts.on("--list-tools", "List available SWAIG functions") do
70
+ @options[:list_tools] = true
71
+ end
72
+
73
+ opts.on("--exec NAME", "Execute a SWAIG function by name") do |v|
74
+ @options[:exec] = v
75
+ end
76
+
77
+ opts.on("--param KEY=VALUE", "Set a parameter (repeatable)") do |v|
78
+ key, value = v.split('=', 2)
79
+ if key && value
80
+ # Try to parse as JSON value (number, bool, etc.)
81
+ @options[:params][key] = parse_param_value(value)
82
+ else
83
+ $stderr.puts "Warning: ignoring malformed --param '#{v}' (expected key=value)"
84
+ end
85
+ end
86
+
87
+ opts.on("--raw", "Output compact JSON instead of pretty-printed") do
88
+ @options[:raw] = true
89
+ end
90
+
91
+ opts.on("--verbose", "Show request/response details") do
92
+ @options[:verbose] = true
93
+ end
94
+
95
+ opts.on("-h", "--help", "Show this help") do
96
+ puts opts
97
+ exit 0
98
+ end
99
+ end
100
+
101
+ parser.parse!(argv)
102
+ end
103
+
104
+ def validate_options!
105
+ unless @options[:url]
106
+ $stderr.puts "Error: --url is required"
107
+ exit 1
108
+ end
109
+
110
+ actions = [@options[:dump_swml], @options[:list_tools], !!@options[:exec]].count(true)
111
+ if actions == 0
112
+ $stderr.puts "Error: specify one of --dump-swml, --list-tools, or --exec NAME"
113
+ exit 1
114
+ elsif actions > 1
115
+ $stderr.puts "Error: specify only one of --dump-swml, --list-tools, or --exec NAME"
116
+ exit 1
117
+ end
118
+ end
119
+
120
+ def parse_param_value(value)
121
+ case value
122
+ when 'true' then true
123
+ when 'false' then false
124
+ when 'null', 'nil' then nil
125
+ when /\A-?\d+\z/ then value.to_i
126
+ when /\A-?\d+\.\d+\z/ then value.to_f
127
+ else value
128
+ end
129
+ end
130
+
131
+ def parsed_uri
132
+ @parsed_uri ||= URI.parse(@options[:url])
133
+ end
134
+
135
+ def base_path
136
+ path = parsed_uri.path
137
+ path = '/' if path.nil? || path.empty?
138
+ path.chomp('/')
139
+ end
140
+
141
+ def auth_header
142
+ user = parsed_uri.user
143
+ pass = parsed_uri.password
144
+ if user && pass
145
+ "Basic " + ["#{URI.decode_www_form_component(user)}:#{URI.decode_www_form_component(pass)}"].pack('m0')
146
+ else
147
+ nil
148
+ end
149
+ end
150
+
151
+ def make_request(method, path, body: nil)
152
+ full_path = base_path == '/' ? path : "#{base_path}#{path}"
153
+ full_path = '/' if full_path.empty?
154
+
155
+ uri = parsed_uri.dup
156
+ uri.path = full_path
157
+ uri.user = nil
158
+ uri.password = nil
159
+
160
+ if @options[:verbose]
161
+ $stderr.puts ">> #{method.upcase} #{uri}"
162
+ $stderr.puts ">> Authorization: Basic <redacted>" if auth_header
163
+ if body
164
+ $stderr.puts ">> Content-Type: application/json"
165
+ $stderr.puts ">> Body: #{body}"
166
+ end
167
+ $stderr.puts ""
168
+ end
169
+
170
+ http = Net::HTTP.new(uri.host, uri.port)
171
+ http.use_ssl = (uri.scheme == 'https')
172
+ http.open_timeout = 10
173
+ http.read_timeout = 30
174
+
175
+ if method == :get
176
+ req = Net::HTTP::Get.new(uri.request_uri)
177
+ else
178
+ req = Net::HTTP::Post.new(uri.request_uri)
179
+ req.body = body
180
+ req['Content-Type'] = 'application/json'
181
+ end
182
+
183
+ req['Authorization'] = auth_header if auth_header
184
+
185
+ response = http.request(req)
186
+
187
+ if @options[:verbose]
188
+ $stderr.puts "<< HTTP #{response.code} #{response.message}"
189
+ response.each_header { |k, v| $stderr.puts "<< #{k}: #{v}" }
190
+ $stderr.puts ""
191
+ end
192
+
193
+ response
194
+ end
195
+
196
+ def format_json(data)
197
+ if @options[:raw]
198
+ JSON.generate(data)
199
+ else
200
+ JSON.pretty_generate(data)
201
+ end
202
+ end
203
+
204
+ def dump_swml
205
+ response = make_request(:get, '/')
206
+
207
+ unless response.is_a?(Net::HTTPSuccess)
208
+ $stderr.puts "Error: HTTP #{response.code} #{response.message}"
209
+ $stderr.puts response.body if @options[:verbose]
210
+ exit 1
211
+ end
212
+
213
+ data = JSON.parse(response.body)
214
+ puts format_json(data)
215
+ rescue JSON::ParserError => e
216
+ $stderr.puts "Error: invalid JSON response: #{e.message}"
217
+ $stderr.puts response.body if @options[:verbose]
218
+ exit 1
219
+ end
220
+
221
+ def list_tools
222
+ response = make_request(:get, '/')
223
+
224
+ unless response.is_a?(Net::HTTPSuccess)
225
+ $stderr.puts "Error: HTTP #{response.code} #{response.message}"
226
+ exit 1
227
+ end
228
+
229
+ swml = JSON.parse(response.body)
230
+ functions = extract_functions(swml)
231
+
232
+ if functions.empty?
233
+ puts "No SWAIG functions found."
234
+ return
235
+ end
236
+
237
+ puts "SWAIG Functions:"
238
+ puts "-" * 60
239
+ functions.each do |func|
240
+ name = func['function'] || '(unnamed)'
241
+ desc = func['description'] || '(no description)'
242
+ puts " #{name}"
243
+ puts " #{desc}"
244
+
245
+ params = func['parameters']
246
+ if params.is_a?(Hash) && params['properties'].is_a?(Hash) && !params['properties'].empty?
247
+ puts " Parameters:"
248
+ params['properties'].each do |pname, pdef|
249
+ ptype = pdef['type'] || 'any' rescue 'any'
250
+ pdesc = pdef['description'] || '' rescue ''
251
+ required = (params['required'] || []).include?(pname) ? ' (required)' : ''
252
+ puts " - #{pname}: #{ptype}#{required} #{pdesc}"
253
+ end
254
+ end
255
+ puts ""
256
+ end
257
+ rescue JSON::ParserError => e
258
+ $stderr.puts "Error: invalid JSON response: #{e.message}"
259
+ exit 1
260
+ end
261
+
262
+ def exec_function
263
+ func_name = @options[:exec]
264
+ payload = {
265
+ 'function' => func_name,
266
+ 'argument' => {
267
+ 'parsed' => [@options[:params]]
268
+ }
269
+ }
270
+
271
+ response = make_request(:post, '/swaig', body: JSON.generate(payload))
272
+
273
+ unless response.is_a?(Net::HTTPSuccess)
274
+ $stderr.puts "Error: HTTP #{response.code} #{response.message}"
275
+ $stderr.puts response.body if @options[:verbose]
276
+ exit 1
277
+ end
278
+
279
+ data = JSON.parse(response.body)
280
+ puts format_json(data)
281
+ rescue JSON::ParserError => e
282
+ $stderr.puts "Error: invalid JSON response: #{e.message}"
283
+ exit 1
284
+ end
285
+
286
+ def extract_functions(swml)
287
+ functions = []
288
+
289
+ # Navigate the SWML structure to find AI → SWAIG → functions
290
+ sections = swml['sections'] || {}
291
+ main = sections['main'] || []
292
+
293
+ main.each do |verb|
294
+ if verb.is_a?(Hash) && verb.key?('ai')
295
+ ai = verb['ai']
296
+ swaig = ai['SWAIG'] || {}
297
+ funcs = swaig['functions'] || []
298
+ functions.concat(funcs)
299
+ end
300
+ end
301
+
302
+ functions
303
+ end
304
+ end
305
+ end
306
+
307
+ # Run when invoked directly
308
+ if __FILE__ == $PROGRAM_NAME
309
+ SwaigTest::CLI.new.run
310
+ end