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,79 @@
|
|
|
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_relative '../swaig/function_result'
|
|
9
|
+
|
|
10
|
+
module SignalWire
|
|
11
|
+
module Prefabs
|
|
12
|
+
# Prefab agent for collecting answers to a series of questions.
|
|
13
|
+
#
|
|
14
|
+
# agent = InfoGatherer.new(
|
|
15
|
+
# questions: [
|
|
16
|
+
# { 'key_name' => 'full_name', 'question_text' => 'What is your full name?' },
|
|
17
|
+
# { 'key_name' => 'email', 'question_text' => 'Email?', 'confirm' => true }
|
|
18
|
+
# ]
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
class InfoGatherer
|
|
22
|
+
attr_reader :questions, :name, :route
|
|
23
|
+
|
|
24
|
+
def initialize(questions:, name: 'info_gatherer', route: '/info_gatherer', **_opts)
|
|
25
|
+
raise ArgumentError, 'questions must be a non-empty Array' unless questions.is_a?(Array) && !questions.empty?
|
|
26
|
+
questions.each_with_index do |q, i|
|
|
27
|
+
raise ArgumentError, "Question #{i} missing key_name" unless q['key_name'] || q[:key_name]
|
|
28
|
+
raise ArgumentError, "Question #{i} missing question_text" unless q['question_text'] || q[:question_text]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@questions = questions.map { |q| q.transform_keys(&:to_s) }
|
|
32
|
+
@name = name
|
|
33
|
+
@route = route
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Tool definitions this prefab provides.
|
|
37
|
+
def tools
|
|
38
|
+
%w[start_questions submit_answer]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Build the prompt sections.
|
|
42
|
+
def prompt_sections
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
'title' => 'Info Gatherer',
|
|
46
|
+
'body' => 'You need to gather answers to a series of questions. ' \
|
|
47
|
+
'Call start_questions to get the first question, then submit_answer after each response.'
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Global data for initial state.
|
|
53
|
+
def global_data
|
|
54
|
+
{
|
|
55
|
+
'info_gatherer' => {
|
|
56
|
+
'questions' => @questions,
|
|
57
|
+
'question_index' => 0,
|
|
58
|
+
'answers' => []
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Tool handler: start_questions
|
|
64
|
+
def handle_start(_args, _raw_data)
|
|
65
|
+
q = @questions.first
|
|
66
|
+
Swaig::FunctionResult.new(
|
|
67
|
+
"[Question 1 of #{@questions.size}]: \"#{q['question_text']}\""
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Tool handler: submit_answer
|
|
72
|
+
def handle_submit(args, _raw_data)
|
|
73
|
+
answer = args['answer'] || ''
|
|
74
|
+
# In a real implementation, state would be tracked via global_data.
|
|
75
|
+
Swaig::FunctionResult.new("Answer recorded: #{answer}")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
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_relative '../swaig/function_result'
|
|
9
|
+
|
|
10
|
+
module SignalWire
|
|
11
|
+
module Prefabs
|
|
12
|
+
# Prefab agent for greeting callers and transferring them to departments.
|
|
13
|
+
#
|
|
14
|
+
# agent = Receptionist.new(
|
|
15
|
+
# departments: [
|
|
16
|
+
# { 'name' => 'sales', 'description' => 'Product inquiries', 'number' => '+15551235555' },
|
|
17
|
+
# { 'name' => 'support', 'description' => 'Technical help', 'number' => '+15551236666' }
|
|
18
|
+
# ]
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
class Receptionist
|
|
22
|
+
attr_reader :departments, :name, :route, :greeting
|
|
23
|
+
|
|
24
|
+
def initialize(departments:, name: 'receptionist', route: '/receptionist',
|
|
25
|
+
greeting: 'Thank you for calling. How can I help you today?', **_opts)
|
|
26
|
+
raise ArgumentError, 'departments must be a non-empty Array' unless departments.is_a?(Array) && !departments.empty?
|
|
27
|
+
departments.each_with_index do |d, i|
|
|
28
|
+
d = d.transform_keys(&:to_s)
|
|
29
|
+
raise ArgumentError, "Department #{i} missing 'name'" unless d['name']
|
|
30
|
+
raise ArgumentError, "Department #{i} missing 'number'" unless d['number']
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@departments = departments.map { |d| d.transform_keys(&:to_s) }
|
|
34
|
+
@greeting = greeting
|
|
35
|
+
@name = name
|
|
36
|
+
@route = route
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def tools
|
|
40
|
+
%w[transfer_to_department collect_caller_info]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def prompt_sections
|
|
44
|
+
bullets = @departments.map { |d| "#{d['name']}: #{d['description'] || d['name']} (#{d['number']})" }
|
|
45
|
+
[
|
|
46
|
+
{
|
|
47
|
+
'title' => 'Receptionist',
|
|
48
|
+
'body' => @greeting,
|
|
49
|
+
'bullets' => bullets
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def global_data
|
|
55
|
+
{
|
|
56
|
+
'departments' => @departments,
|
|
57
|
+
'caller_info' => {}
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def handle_transfer(args, _raw_data)
|
|
62
|
+
dept_name = args['department']
|
|
63
|
+
dept = @departments.find { |d| d['name'] == dept_name }
|
|
64
|
+
if dept
|
|
65
|
+
result = Swaig::FunctionResult.new("Transferring you to #{dept_name} now.")
|
|
66
|
+
result.connect(dept['number'])
|
|
67
|
+
result
|
|
68
|
+
else
|
|
69
|
+
Swaig::FunctionResult.new("I couldn't find that department. Available departments: #{@departments.map { |d| d['name'] }.join(', ')}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
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_relative '../swaig/function_result'
|
|
9
|
+
|
|
10
|
+
module SignalWire
|
|
11
|
+
module Prefabs
|
|
12
|
+
# Prefab agent for conducting automated surveys.
|
|
13
|
+
#
|
|
14
|
+
# agent = Survey.new(
|
|
15
|
+
# survey_name: 'Customer Satisfaction',
|
|
16
|
+
# questions: [
|
|
17
|
+
# { 'id' => 'satisfaction', 'text' => 'How satisfied were you?', 'type' => 'rating', 'scale' => 5 }
|
|
18
|
+
# ]
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
class Survey
|
|
22
|
+
attr_reader :survey_name, :questions, :name, :route
|
|
23
|
+
|
|
24
|
+
def initialize(survey_name:, questions:, introduction: nil, conclusion: nil,
|
|
25
|
+
name: 'survey', route: '/survey', **_opts)
|
|
26
|
+
raise ArgumentError, 'questions must be a non-empty Array' unless questions.is_a?(Array) && !questions.empty?
|
|
27
|
+
|
|
28
|
+
@survey_name = survey_name
|
|
29
|
+
@questions = questions.map { |q| q.transform_keys(&:to_s) }
|
|
30
|
+
@introduction = introduction || "Welcome to the #{survey_name}. Let's get started."
|
|
31
|
+
@conclusion = conclusion || 'Thank you for completing the survey!'
|
|
32
|
+
@name = name
|
|
33
|
+
@route = route
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def tools
|
|
37
|
+
%w[start_survey submit_survey_answer get_survey_summary]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def prompt_sections
|
|
41
|
+
[
|
|
42
|
+
{
|
|
43
|
+
'title' => "Survey: #{@survey_name}",
|
|
44
|
+
'body' => @introduction,
|
|
45
|
+
'bullets' => @questions.map { |q| "#{q['id']}: #{q['text']} (#{q['type'] || 'open_ended'})" }
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def global_data
|
|
51
|
+
{
|
|
52
|
+
'survey' => {
|
|
53
|
+
'name' => @survey_name,
|
|
54
|
+
'questions' => @questions,
|
|
55
|
+
'current' => 0,
|
|
56
|
+
'responses' => {}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def handle_start(_args, _raw_data)
|
|
62
|
+
q = @questions.first
|
|
63
|
+
Swaig::FunctionResult.new("#{@introduction}\n\n[Question 1 of #{@questions.size}]: #{q['text']}")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def handle_submit(args, _raw_data)
|
|
67
|
+
Swaig::FunctionResult.new("Response recorded: #{args['answer']}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def handle_summary(_args, _raw_data)
|
|
71
|
+
Swaig::FunctionResult.new(@conclusion)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module SignalWire
|
|
6
|
+
module Relay
|
|
7
|
+
# Raised when an action times out waiting for completion.
|
|
8
|
+
class ActionTimeoutError < StandardError; end
|
|
9
|
+
|
|
10
|
+
# Base class for async action handles (play, record, detect, etc.).
|
|
11
|
+
#
|
|
12
|
+
# Holds a control_id and back-reference to the Call. Resolves when the
|
|
13
|
+
# server sends a terminal event for this control_id.
|
|
14
|
+
#
|
|
15
|
+
# Uses Ruby's Queue for blocking wait semantics.
|
|
16
|
+
class Action
|
|
17
|
+
attr_reader :control_id, :call, :result, :completed
|
|
18
|
+
|
|
19
|
+
def initialize(call, control_id, terminal_event, terminal_states)
|
|
20
|
+
@call = call
|
|
21
|
+
@control_id = control_id
|
|
22
|
+
@terminal_event = terminal_event
|
|
23
|
+
@terminal_states = terminal_states
|
|
24
|
+
@result = nil
|
|
25
|
+
@completed = false
|
|
26
|
+
@mutex = Mutex.new
|
|
27
|
+
@condition = ConditionVariable.new
|
|
28
|
+
@on_completed = nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Set the on_completed callback.
|
|
32
|
+
def on_completed(&block)
|
|
33
|
+
@on_completed = block
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Called internally to set the on_completed callback from options.
|
|
37
|
+
def _set_on_completed(callback)
|
|
38
|
+
@on_completed = callback
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Called by Call when an event matches our control_id.
|
|
42
|
+
def _check_event(event)
|
|
43
|
+
state = event.params['state'] || ''
|
|
44
|
+
if @terminal_states.include?(state) && !@completed
|
|
45
|
+
_resolve(event)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Mark the action as completed and fire the on_completed callback.
|
|
50
|
+
def _resolve(event)
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
return if @completed
|
|
53
|
+
|
|
54
|
+
@result = event
|
|
55
|
+
@completed = true
|
|
56
|
+
@condition.broadcast
|
|
57
|
+
end
|
|
58
|
+
if @on_completed
|
|
59
|
+
begin
|
|
60
|
+
@on_completed.call(event)
|
|
61
|
+
rescue => e
|
|
62
|
+
$stderr.puts "[RELAY] Error in on_completed callback for #{@control_id}: #{e.message}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Wait for the action to complete. Returns the terminal event.
|
|
68
|
+
# Raises ActionTimeoutError if timeout is specified and exceeded.
|
|
69
|
+
def wait(timeout: nil)
|
|
70
|
+
@mutex.synchronize do
|
|
71
|
+
return @result if @completed
|
|
72
|
+
|
|
73
|
+
if timeout
|
|
74
|
+
deadline = Time.now + timeout
|
|
75
|
+
while !@completed
|
|
76
|
+
remaining = deadline - Time.now
|
|
77
|
+
if remaining <= 0
|
|
78
|
+
raise ActionTimeoutError, "Action #{@control_id} timed out after #{timeout}s"
|
|
79
|
+
end
|
|
80
|
+
@condition.wait(@mutex, remaining)
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
@condition.wait(@mutex) until @completed
|
|
84
|
+
end
|
|
85
|
+
@result
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def done?
|
|
90
|
+
@completed
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
alias_method :is_done?, :done?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Handle for an active play operation.
|
|
97
|
+
class PlayAction < Action
|
|
98
|
+
def initialize(call, control_id)
|
|
99
|
+
super(call, control_id, EVENT_CALL_PLAY,
|
|
100
|
+
[PLAY_STATE_FINISHED, PLAY_STATE_ERROR])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def stop
|
|
104
|
+
@call._execute('play.stop', { 'control_id' => @control_id })
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def pause
|
|
108
|
+
@call._execute('play.pause', { 'control_id' => @control_id })
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def resume
|
|
112
|
+
@call._execute('play.resume', { 'control_id' => @control_id })
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def volume(vol)
|
|
116
|
+
@call._execute('play.volume', { 'control_id' => @control_id, 'volume' => vol })
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Handle for an active record operation.
|
|
121
|
+
class RecordAction < Action
|
|
122
|
+
def initialize(call, control_id)
|
|
123
|
+
super(call, control_id, EVENT_CALL_RECORD,
|
|
124
|
+
[RECORD_STATE_FINISHED, RECORD_STATE_NO_INPUT])
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def stop
|
|
128
|
+
@call._execute('record.stop', { 'control_id' => @control_id })
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def pause(behavior: nil)
|
|
132
|
+
params = { 'control_id' => @control_id }
|
|
133
|
+
params['behavior'] = behavior if behavior
|
|
134
|
+
@call._execute('record.pause', params)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def resume
|
|
138
|
+
@call._execute('record.resume', { 'control_id' => @control_id })
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Handle for an active detect operation.
|
|
143
|
+
class DetectAction < Action
|
|
144
|
+
def initialize(call, control_id)
|
|
145
|
+
super(call, control_id, EVENT_CALL_DETECT, %w[finished error])
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Detect delivers results continuously. Resolve on first result or
|
|
149
|
+
# when finished/error.
|
|
150
|
+
def _check_event(event)
|
|
151
|
+
detect = event.params['detect'] || {}
|
|
152
|
+
state = event.params['state'] || ''
|
|
153
|
+
if (!detect.empty? || @terminal_states.include?(state)) && !@completed
|
|
154
|
+
_resolve(event)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def stop
|
|
159
|
+
@call._execute('detect.stop', { 'control_id' => @control_id })
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Handle for play_and_collect or standalone collect.
|
|
164
|
+
class CollectAction < Action
|
|
165
|
+
def initialize(call, control_id)
|
|
166
|
+
super(call, control_id, EVENT_CALL_COLLECT,
|
|
167
|
+
%w[finished error no_input no_match])
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# play_and_collect shares a control_id across play and collect
|
|
171
|
+
# phases. Only resolve on collect events, not play events.
|
|
172
|
+
def _check_event(event)
|
|
173
|
+
return unless event.event_type == EVENT_CALL_COLLECT
|
|
174
|
+
|
|
175
|
+
result_data = event.params['result'] || {}
|
|
176
|
+
if !result_data.empty? && !@completed
|
|
177
|
+
_resolve(event)
|
|
178
|
+
else
|
|
179
|
+
super(event)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def stop
|
|
184
|
+
@call._execute('play_and_collect.stop', { 'control_id' => @control_id })
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def volume(vol)
|
|
188
|
+
@call._execute('play_and_collect.volume', {
|
|
189
|
+
'control_id' => @control_id, 'volume' => vol
|
|
190
|
+
})
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def start_input_timers
|
|
194
|
+
@call._execute('collect.start_input_timers', { 'control_id' => @control_id })
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Handle for standalone calling.collect (without play).
|
|
199
|
+
class StandaloneCollectAction < Action
|
|
200
|
+
def initialize(call, control_id)
|
|
201
|
+
super(call, control_id, EVENT_CALL_COLLECT,
|
|
202
|
+
%w[finished error no_input no_match])
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def _check_event(event)
|
|
206
|
+
return unless event.event_type == EVENT_CALL_COLLECT
|
|
207
|
+
|
|
208
|
+
result_data = event.params['result'] || {}
|
|
209
|
+
state = event.params['state'] || ''
|
|
210
|
+
if (!result_data.empty? || @terminal_states.include?(state)) && !@completed
|
|
211
|
+
_resolve(event)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def stop
|
|
216
|
+
@call._execute('collect.stop', { 'control_id' => @control_id })
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def start_input_timers
|
|
220
|
+
@call._execute('collect.start_input_timers', { 'control_id' => @control_id })
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Handle for send_fax or receive_fax.
|
|
225
|
+
class FaxAction < Action
|
|
226
|
+
def initialize(call, control_id, method_prefix)
|
|
227
|
+
super(call, control_id, EVENT_CALL_FAX, %w[finished error])
|
|
228
|
+
@method_prefix = method_prefix
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def stop
|
|
232
|
+
@call._execute("#{@method_prefix}.stop", { 'control_id' => @control_id })
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Handle for an active tap operation.
|
|
237
|
+
class TapAction < Action
|
|
238
|
+
def initialize(call, control_id)
|
|
239
|
+
super(call, control_id, EVENT_CALL_TAP, %w[finished])
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def stop
|
|
243
|
+
@call._execute('tap.stop', { 'control_id' => @control_id })
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Handle for an active stream operation.
|
|
248
|
+
class StreamAction < Action
|
|
249
|
+
def initialize(call, control_id)
|
|
250
|
+
super(call, control_id, EVENT_CALL_STREAM, %w[finished])
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def stop
|
|
254
|
+
@call._execute('stream.stop', { 'control_id' => @control_id })
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Handle for an active pay operation.
|
|
259
|
+
class PayAction < Action
|
|
260
|
+
def initialize(call, control_id)
|
|
261
|
+
super(call, control_id, EVENT_CALL_PAY, %w[finished error])
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def stop
|
|
265
|
+
@call._execute('pay.stop', { 'control_id' => @control_id })
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Handle for an active transcribe operation.
|
|
270
|
+
class TranscribeAction < Action
|
|
271
|
+
def initialize(call, control_id)
|
|
272
|
+
super(call, control_id, EVENT_CALL_TRANSCRIBE, %w[finished])
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def stop
|
|
276
|
+
@call._execute('transcribe.stop', { 'control_id' => @control_id })
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Handle for an active AI agent session.
|
|
281
|
+
class AIAction < Action
|
|
282
|
+
def initialize(call, control_id)
|
|
283
|
+
super(call, control_id, 'calling.call.ai', %w[finished error])
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def stop
|
|
287
|
+
@call._execute('ai.stop', { 'control_id' => @control_id })
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|