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.
- checksums.yaml +7 -0
- data/README.md +79 -0
- data/bin/swaig-test +310 -0
- data/lib/signalwire_agents/agent/agent_base.rb +1171 -0
- data/lib/signalwire_agents/contexts/context_builder.rb +622 -0
- data/lib/signalwire_agents/datamap/data_map.rb +279 -0
- data/lib/signalwire_agents/logging.rb +92 -0
- data/lib/signalwire_agents/prefabs/concierge.rb +92 -0
- data/lib/signalwire_agents/prefabs/faq_bot.rb +67 -0
- data/lib/signalwire_agents/prefabs/info_gatherer.rb +79 -0
- data/lib/signalwire_agents/prefabs/receptionist.rb +74 -0
- data/lib/signalwire_agents/prefabs/survey.rb +75 -0
- data/lib/signalwire_agents/relay/action.rb +291 -0
- data/lib/signalwire_agents/relay/call.rb +523 -0
- data/lib/signalwire_agents/relay/client.rb +671 -0
- data/lib/signalwire_agents/relay/constants.rb +124 -0
- data/lib/signalwire_agents/relay/message.rb +137 -0
- data/lib/signalwire_agents/relay/relay_event.rb +670 -0
- data/lib/signalwire_agents/rest/http_client.rb +147 -0
- data/lib/signalwire_agents/rest/namespaces/addresses.rb +19 -0
- data/lib/signalwire_agents/rest/namespaces/calling.rb +179 -0
- data/lib/signalwire_agents/rest/namespaces/chat.rb +18 -0
- data/lib/signalwire_agents/rest/namespaces/compat.rb +229 -0
- data/lib/signalwire_agents/rest/namespaces/datasphere.rb +39 -0
- data/lib/signalwire_agents/rest/namespaces/fabric.rb +175 -0
- data/lib/signalwire_agents/rest/namespaces/imported_numbers.rb +18 -0
- data/lib/signalwire_agents/rest/namespaces/logs.rb +46 -0
- data/lib/signalwire_agents/rest/namespaces/lookup.rb +18 -0
- data/lib/signalwire_agents/rest/namespaces/mfa.rb +26 -0
- data/lib/signalwire_agents/rest/namespaces/number_groups.rb +32 -0
- data/lib/signalwire_agents/rest/namespaces/phone_numbers.rb +20 -0
- data/lib/signalwire_agents/rest/namespaces/project.rb +33 -0
- data/lib/signalwire_agents/rest/namespaces/pubsub.rb +18 -0
- data/lib/signalwire_agents/rest/namespaces/queues.rb +28 -0
- data/lib/signalwire_agents/rest/namespaces/recordings.rb +18 -0
- data/lib/signalwire_agents/rest/namespaces/registry.rb +67 -0
- data/lib/signalwire_agents/rest/namespaces/short_codes.rb +26 -0
- data/lib/signalwire_agents/rest/namespaces/sip_profile.rb +22 -0
- data/lib/signalwire_agents/rest/namespaces/verified_callers.rb +24 -0
- data/lib/signalwire_agents/rest/namespaces/video.rb +129 -0
- data/lib/signalwire_agents/rest/signalwire_client.rb +110 -0
- data/lib/signalwire_agents/security/session_manager.rb +124 -0
- data/lib/signalwire_agents/server/agent_server.rb +260 -0
- data/lib/signalwire_agents/skills/builtin/api_ninjas_trivia.rb +91 -0
- data/lib/signalwire_agents/skills/builtin/claude_skills.rb +92 -0
- data/lib/signalwire_agents/skills/builtin/custom_skills.rb +54 -0
- data/lib/signalwire_agents/skills/builtin/datasphere.rb +141 -0
- data/lib/signalwire_agents/skills/builtin/datasphere_serverless.rb +107 -0
- data/lib/signalwire_agents/skills/builtin/datetime.rb +97 -0
- data/lib/signalwire_agents/skills/builtin/google_maps.rb +168 -0
- data/lib/signalwire_agents/skills/builtin/info_gatherer.rb +189 -0
- data/lib/signalwire_agents/skills/builtin/joke.rb +65 -0
- data/lib/signalwire_agents/skills/builtin/math.rb +176 -0
- data/lib/signalwire_agents/skills/builtin/mcp_gateway.rb +121 -0
- data/lib/signalwire_agents/skills/builtin/native_vector_search.rb +116 -0
- data/lib/signalwire_agents/skills/builtin/play_background_file.rb +86 -0
- data/lib/signalwire_agents/skills/builtin/spider.rb +142 -0
- data/lib/signalwire_agents/skills/builtin/swml_transfer.rb +118 -0
- data/lib/signalwire_agents/skills/builtin/weather_api.rb +85 -0
- data/lib/signalwire_agents/skills/builtin/web_search.rb +123 -0
- data/lib/signalwire_agents/skills/builtin/wikipedia_search.rb +109 -0
- data/lib/signalwire_agents/skills/skill_base.rb +58 -0
- data/lib/signalwire_agents/skills/skill_manager.rb +85 -0
- data/lib/signalwire_agents/skills/skill_registry.rb +76 -0
- data/lib/signalwire_agents/swaig/function_result.rb +777 -0
- data/lib/signalwire_agents/swml/document.rb +84 -0
- data/lib/signalwire_agents/swml/schema.json +12250 -0
- data/lib/signalwire_agents/swml/schema.rb +81 -0
- data/lib/signalwire_agents/swml/service.rb +304 -0
- data/lib/signalwire_agents/version.rb +5 -0
- data/lib/signalwire_agents.rb +19 -0
- 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
|