voiceml 0.9.2 → 0.9.3

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -13
  3. data/lib/voiceml/client.rb +53 -13
  4. data/lib/voiceml/errors.rb +15 -2
  5. data/lib/voiceml/models/accounts.rb +47 -3
  6. data/lib/voiceml/models/applications.rb +6 -0
  7. data/lib/voiceml/models/assistants_v1.rb +40 -0
  8. data/lib/voiceml/models/calls.rb +4 -0
  9. data/lib/voiceml/models/common.rb +35 -4
  10. data/lib/voiceml/models/conferences.rb +8 -0
  11. data/lib/voiceml/models/conversations_v1.rb +131 -29
  12. data/lib/voiceml/models/diagnostics.rb +33 -12
  13. data/lib/voiceml/models/exports.rb +58 -0
  14. data/lib/voiceml/models/incoming_phone_numbers.rb +4 -0
  15. data/lib/voiceml/models/media.rb +4 -0
  16. data/lib/voiceml/models/messages.rb +29 -0
  17. data/lib/voiceml/models/messaging_v1.rb +35 -2
  18. data/lib/voiceml/models/monitor_v1.rb +54 -0
  19. data/lib/voiceml/models/notifications.rb +2 -0
  20. data/lib/voiceml/models/outgoing_caller_ids.rb +6 -0
  21. data/lib/voiceml/models/payments.rb +2 -0
  22. data/lib/voiceml/models/pricing.rb +38 -4
  23. data/lib/voiceml/models/queues.rb +8 -0
  24. data/lib/voiceml/models/recordings.rb +5 -1
  25. data/lib/voiceml/models/routes_v2.rb +6 -2
  26. data/lib/voiceml/models/sip.rb +34 -6
  27. data/lib/voiceml/models/sip_registrations.rb +49 -0
  28. data/lib/voiceml/models/siprec.rb +5 -17
  29. data/lib/voiceml/models/streams.rb +4 -16
  30. data/lib/voiceml/models/transcriptions.rb +35 -4
  31. data/lib/voiceml/models/usage_records.rb +48 -0
  32. data/lib/voiceml/models/user_defined_messages.rb +2 -0
  33. data/lib/voiceml/models/voice_v1.rb +29 -19
  34. data/lib/voiceml/request_validator.rb +125 -0
  35. data/lib/voiceml/resources/accounts.rb +75 -0
  36. data/lib/voiceml/resources/applications.rb +3 -1
  37. data/lib/voiceml/resources/assistants_v1.rb +53 -77
  38. data/lib/voiceml/resources/base.rb +138 -16
  39. data/lib/voiceml/resources/calls.rb +34 -49
  40. data/lib/voiceml/resources/conferences.rb +26 -5
  41. data/lib/voiceml/resources/conversations_v1.rb +147 -172
  42. data/lib/voiceml/resources/diagnostics.rb +20 -35
  43. data/lib/voiceml/resources/exports.rb +36 -0
  44. data/lib/voiceml/resources/incoming_phone_numbers.rb +53 -32
  45. data/lib/voiceml/resources/messages.rb +79 -15
  46. data/lib/voiceml/resources/messaging_v1.rb +47 -13
  47. data/lib/voiceml/resources/monitor_v1.rb +68 -0
  48. data/lib/voiceml/resources/notifications.rb +1 -1
  49. data/lib/voiceml/resources/outgoing_caller_ids.rb +77 -0
  50. data/lib/voiceml/resources/pricing.rb +13 -5
  51. data/lib/voiceml/resources/queues.rb +6 -5
  52. data/lib/voiceml/resources/recordings.rb +39 -5
  53. data/lib/voiceml/resources/routes_v2.rb +10 -4
  54. data/lib/voiceml/resources/sip.rb +30 -11
  55. data/lib/voiceml/resources/transcriptions.rb +40 -0
  56. data/lib/voiceml/resources/usage_records.rb +51 -0
  57. data/lib/voiceml/resources/voice_v1.rb +31 -38
  58. data/lib/voiceml/transport.rb +277 -230
  59. data/lib/voiceml/version.rb +1 -1
  60. data/lib/voiceml.rb +14 -15
  61. metadata +18 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce6f40c38ff553e748881a1b57f940e977cd24983da928988a7dcc77640cc32d
4
- data.tar.gz: d6d26a2ab6ee10d9df572f24c6b667cb87cb03bb68ef40234fb05308db3d3789
3
+ metadata.gz: 758b40e32d36539e838ab6418d8a978b089ebfebc7754847049e47fb281e4336
4
+ data.tar.gz: 057dc821a03507d3883edc839e9e5e2ead794e162cef2ff76ea6bf1d6f9787d6
5
5
  SHA512:
6
- metadata.gz: 28fe625cbe02ccd47894507e8c28d8eecd06f0caae4aea040f5e21af82980072e39a74aa7cb788fc885f9b24e58239099a87dac639a79b9f3a3c248769e0a652
7
- data.tar.gz: 85fd838a671b848768a791d590fbcdefa40c49e89d80b8f2c2c98f475af7035489a727ca3d1ecd7e5988296a5a62f32e3784344fc9fcc11f24beec16d5d9619e
6
+ metadata.gz: d88a3dc916f95b965f0899f910a0fee987d85b9d81ee33dc2dec8ce9b3d71178209e7c07999fb599d76cf172ad620b84d144459e2d3cb9ecde7e9a87e2a60dc1
7
+ data.tar.gz: 9aa62aaa635a1e2021ded627867cc6954eb389c16732c67ab9470fad749a6acbe64f448b7bd92dae412798196d024022d83c33ce0203f6718d0158330da8f5d5
data/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  The official Ruby client for the [VoiceML REST API](https://voicetel.com/docs/api/v0.9/voiceml/) — Twilio-compatible outbound voice and answering-machine-detection from VoiceTel, with idiomatic snake_case kwargs, structured errors, and a Twilio-compatible wire format.
4
4
 
5
- ![Version](https://img.shields.io/badge/version-0.9.2-blue)
6
- ![Ruby](https://img.shields.io/badge/ruby-3.0%2B-red)
5
+ ![Version](https://img.shields.io/badge/version-0.9.3-blue)
6
+ ![Ruby](https://img.shields.io/badge/ruby-3.1%2B-red)
7
7
  ![License](https://img.shields.io/badge/license-MIT-green)
8
- ![Tests](https://img.shields.io/badge/tests-188%20examples-brightgreen)
8
+ ![Tests](https://img.shields.io/badge/tests-227%20examples-brightgreen)
9
9
  ![Gem](https://img.shields.io/badge/gem-voiceml-blue)
10
10
 
11
11
  ## 📚 Table of Contents
@@ -44,9 +44,11 @@ The official Ruby client for the [VoiceML REST API](https://voicetel.com/docs/ap
44
44
  - **Queues** — create, list, update, delete, peek, dequeue (front or specific member).
45
45
  - **Applications** — CRUD on stored TwiML + callback bundles.
46
46
  - **Recordings** — account-wide list, metadata fetch, audio fetch (follows S3 redirect), delete.
47
- - **Messages** — create, fetch, list (To/From/DateSent filters + pagination), update (Body redaction; Status=canceled), delete.
47
+ - **Messages** — create, fetch, list (To/From/DateSent filters + pagination), update (Body redaction; Status=canceled), delete, delivery feedback (`create_feedback`).
48
48
  - **IncomingPhoneNumbers** — list, fetch, update.
49
49
  - **Notifications** — fetch, list.
50
+ - **Monitor Alerts** — `client.monitor_alerts` list (severity + date-window filters) and fetch over the `/v1` `meta` envelope.
51
+ - **Accounts** — `client.accounts.fetch` / `update` / `fetch_balance` / `rotate_key` (key rotation returns the new key on `AccountWithKey#api_key`).
50
52
  - **SIP** — SIP Trunking: Domains (CRUD), CredentialLists + Credentials (CRUD), IpAccessControlLists + IpAddresses (CRUD), Domain↔ACL/CredentialList mappings (historical, Auth/Calls, Auth/Registrations namespaces).
51
53
  - **Routes V2** — Twilio Inbound Processing Region API: `client.routes_v2.sip_domains.fetch(name)` / `update(name, voice_region:, friendly_name:)`.
52
54
  - **Messaging Service** — `client.messaging_v1.services` CRUD (`MG...` services), routed automatically at the `messaging.voicetel.com` product host.
@@ -57,7 +59,7 @@ The official Ruby client for the [VoiceML REST API](https://voicetel.com/docs/ap
57
59
  VoiceML mirrors Twilio's product-per-subdomain model. The whole `conversations_v1` group is routed to `conversations.voicetel.com` and `messaging_v1` to `messaging.voicetel.com`, while everything else stays on the default `voiceml.voicetel.com`. These hosts are derived automatically from `base_url`; a non-`voicetel.com` base (e.g. a self-hosted instance) keeps every product on that single host. Override per product with `messaging_base_url:` / `conversations_base_url:` on the client constructor.
58
60
 
59
61
  ### 🧪 Tested
60
- - **188 examples** with mocked HTTP layer (`webmock`) covering every resource and pagination edge cases — spec drift gets caught at parse time.
62
+ - **227 examples** with mocked HTTP layer (`webmock`) covering every resource and pagination edge cases — spec drift gets caught at parse time.
61
63
  - Integration smoke spec gated by env vars — safe for CI.
62
64
 
63
65
  ### 📦 Clean Distribution
@@ -77,7 +79,7 @@ Or in your `Gemfile`:
77
79
  gem 'voiceml', '~> 0.7'
78
80
  ```
79
81
 
80
- Requires Ruby 3.0 or later.
82
+ Requires Ruby 3.1 or later.
81
83
 
82
84
  ## 🏁 Quickstart
83
85
 
@@ -90,8 +92,8 @@ client = VoiceML::Client.new(
90
92
  )
91
93
 
92
94
  call = client.calls.create(
93
- to: '+18005551234',
94
- from: '+18005550000',
95
+ to: '+14155550100',
96
+ from: '+14155550101',
95
97
  url: 'https://example.com/twiml',
96
98
  machine_detection: 'DetectMessageEnd'
97
99
  )
@@ -105,7 +107,7 @@ end
105
107
 
106
108
  ## 🔑 Authentication
107
109
 
108
- Every endpoint uses **HTTP Basic** with your `AccountSid` as the username and your per-tenant API key as the password — identical to Twilio's auth shape, so credentials issued for Twilio code work here unchanged.
110
+ Every endpoint uses **HTTP Basic** with your `AccountSid` as the username and your per-tenant API key as the password — the same positions Twilio SDKs use, so existing credential-passing code migrates unchanged. VoiceML issues its own credentials; Twilio-issued credentials do not work here.
109
111
 
110
112
  - **Username** = your `AccountSid` (Twilio-format `AC` + 32 hex chars).
111
113
  - **Password** = your per-tenant API key (pass as `api_key:` or, for migration-compatible drop-in, `auth_token:`).
@@ -126,9 +128,11 @@ client.diagnostics.health # uses your AccountSid + key on every call
126
128
  | `client.queues` | create, list, update, delete | peek, dequeue (front or specific member) |
127
129
  | `client.applications` | CRUD on TwiML + callback bundles | |
128
130
  | `client.recordings` | account-wide list, metadata, audio fetch, delete | follows S3 redirect for audio |
129
- | `client.messages` | create, fetch, list, update, delete | To/From/DateSent filters; Body redaction; Status=canceled |
131
+ | `client.messages` | create, fetch, list, update, delete, create_feedback | To/From/DateSent filters; Body redaction; Status=canceled; delivery feedback |
130
132
  | `client.incoming_phone_numbers` | list, fetch, update | |
131
133
  | `client.notifications` | fetch, list | |
134
+ | `client.monitor_alerts` | list, fetch | Monitor v1 Alerts (`/v1` `meta` envelope); severity + date-window filters |
135
+ | `client.accounts` | fetch, update, fetch_balance, rotate_key | rotate_key returns the new API key on `AccountWithKey#api_key` |
132
136
  | `client.messaging_v1.services` | create, list, fetch, update, delete | Messaging Service (`MG...`); routed at `messaging.voicetel.com` |
133
137
  | `client.pricing.v1` / `.v2` | countries.list / fetch, numbers.fetch | Voice, Messaging, PhoneNumbers, Trunking (read-only, default host) |
134
138
  | `client.diagnostics` | `/health`, OpenAPI spec | |
@@ -139,8 +143,8 @@ Methods accept idiomatic snake_case keyword arguments — they're translated to
139
143
  client = VoiceML::Client.new(account_sid: 'AC...', api_key: '...')
140
144
 
141
145
  call = client.calls.create(
142
- to: '+18005551234',
143
- from: '+18005550000',
146
+ to: '+14155550100',
147
+ from: '+14155550101',
144
148
  url: 'https://example.com/twiml'
145
149
  )
146
150
 
@@ -195,7 +199,7 @@ client.calls.each(status: 'completed', page_size: 200) do |call|
195
199
  end
196
200
 
197
201
  # Enumerator form — chain with Enumerable methods
198
- recent = client.messages.each(from: '+18005550000', page_size: 200).first(50)
202
+ recent = client.messages.each(from: '+14155550101', page_size: 200).first(50)
199
203
 
200
204
  client.queues.each do |q|
201
205
  archive(q) if q.current_size.zero?
@@ -18,43 +18,66 @@ require_relative 'resources/conversations_v1'
18
18
  require_relative 'resources/assistants_v1'
19
19
  require_relative 'resources/messaging_v1'
20
20
  require_relative 'resources/pricing'
21
+ require_relative 'resources/accounts'
22
+ require_relative 'resources/outgoing_caller_ids'
23
+ require_relative 'resources/exports'
24
+ require_relative 'resources/usage_records'
25
+ require_relative 'resources/transcriptions'
26
+ require_relative 'resources/monitor_v1'
21
27
 
22
28
  module VoiceML
23
29
  # Synchronous client for the VoiceML REST API.
24
30
  #
25
31
  # VoiceML uses HTTP Basic auth: the `account_sid` (Twilio-format `AC` + 32 hex) is the
26
- # username and `api_key` is the password. Drop-in compatible with the Twilio Ruby SDK
27
- # constructor signature.
32
+ # username and `api_key` is the password, in the same HTTP Basic positions twilio-ruby
33
+ # uses. VoiceML issues its own credentials — the AccountSid plus a VoiceML API key;
34
+ # Twilio-issued credentials do not work here.
35
+ #
36
+ # A client without an `api_key:` is a credential-less diagnostics client: only
37
+ # {VoiceML::DiagnosticsResource} calls work (the spec marks them `security: []`);
38
+ # anything authenticated raises {VoiceML::ConfigurationError}.
39
+ #
40
+ # The client is thread-safe: requests may be issued from any number of threads,
41
+ # each holding its own connection per host.
28
42
  #
29
43
  # @example
30
44
  # client = VoiceML::Client.new(account_sid: 'AC...', api_key: '...')
31
45
  # call = client.calls.create(
32
- # to: '+18005551234',
33
- # from: '+18005550000',
34
- # url: 'https://example.com/twiml'
46
+ # to: '+14155550100',
47
+ # from: '+14155550101',
48
+ # url: 'https://example.com/handler'
35
49
  # )
36
50
  # puts call.sid, call.status
37
51
  class Client
38
52
  attr_reader :calls, :conferences, :queues, :applications, :recordings,
39
53
  :incoming_phone_numbers, :notifications, :diagnostics, :messages,
40
54
  :sip, :routes_v2, :voice_v1, :conversations_v1, :assistants_v1,
41
- :messaging_v1, :pricing
55
+ :messaging_v1, :pricing, :accounts, :outgoing_caller_ids, :exports,
56
+ :usage_records, :transcriptions, :monitor_alerts
42
57
 
43
58
  # @param account_sid [String] Twilio-format AccountSid (`AC` + 32 hex).
44
- # @param api_key [String, nil] per-tenant API key. Pass either `api_key:` or the
45
- # Twilio-compatible alias `auth_token:` (not both — `ArgumentError` if you do).
46
- # @param auth_token [String, nil] Twilio-compatible alias for `api_key`. Lets twilio-ruby
47
- # code (`VoiceML::Client.new(account_sid: sid, auth_token: token)`) work unchanged.
59
+ # @param api_key [String, nil] per-tenant VoiceML API key. Pass either `api_key:` or
60
+ # the Twilio-compatible alias `auth_token:` (not both — `ArgumentError` if you do).
61
+ # Omit both for a credential-less diagnostics-only client.
62
+ # @param auth_token [String, nil] Twilio-compatible alias for `api_key`. Lets
63
+ # credential-passing code written for twilio-ruby keep its call shape; the key
64
+ # itself must be a VoiceML API key.
48
65
  # @param base_url [String] server base URL. Defaults to `https://voiceml.voicetel.com`.
49
- # @param timeout [Numeric] per-request timeout in seconds. Defaults to 30.
50
- # @param max_retries [Integer] retry attempts for 429/5xx and transport errors. Defaults to 2.
66
+ # @param timeout [Numeric] per-request connect/read/write timeout in seconds. Defaults to 30.
67
+ # @param max_retries [Integer] retry attempts. Idempotent requests (GET/HEAD/PUT/DELETE)
68
+ # retry on 429/5xx and transport errors; POST/PATCH retry only on 429 and
69
+ # connect-phase failures. Defaults to 2.
51
70
  # @param user_agent [String, nil] override the `User-Agent` header. Defaults to
52
71
  # `"voiceml-ruby/#{VERSION}"`.
72
+ # @param http_client [Net::HTTP, #call, nil] a `Net::HTTP` used for its own host, or a
73
+ # factory `->(uri) { Net::HTTP.new(uri.host, uri.port) }` supplying every
74
+ # connection. Caller-supplied connections are never finished by the SDK; hosts the
75
+ # object/factory does not cover fall back to SDK-managed connections.
53
76
  # @param messaging_base_url [String, nil] override the host for `messaging_v1`. Defaults to
54
77
  # the `messaging.*.voicetel.com` subdomain derived from `base_url` (else `base_url` itself).
55
78
  # @param conversations_base_url [String, nil] override the host for `conversations_v1`. Defaults
56
79
  # to the `conversations.*.voicetel.com` subdomain derived from `base_url` (else `base_url`).
57
- def initialize(account_sid:, api_key: nil, auth_token: nil,
80
+ def initialize(account_sid: nil, api_key: nil, auth_token: nil,
58
81
  base_url: Transport::DEFAULT_BASE_URL,
59
82
  timeout: Transport::DEFAULT_TIMEOUT,
60
83
  max_retries: Transport::DEFAULT_MAX_RETRIES,
@@ -102,6 +125,12 @@ module VoiceML
102
125
  @assistants_v1 = AssistantsV1Resource.new(@transport)
103
126
  @messaging_v1 = MessagingV1Resource.new(messaging_transport)
104
127
  @pricing = PricingResource.new(@transport)
128
+ @accounts = AccountsResource.new(@transport)
129
+ @outgoing_caller_ids = OutgoingCallerIdsResource.new(@transport)
130
+ @exports = ExportsResource.new(@transport)
131
+ @usage_records = UsageRecordsResource.new(@transport)
132
+ @transcriptions = TranscriptionsResource.new(@transport)
133
+ @monitor_alerts = MonitorAlertsResource.new(@transport)
105
134
  end
106
135
 
107
136
  def account_sid
@@ -111,5 +140,16 @@ module VoiceML
111
140
  def base_url
112
141
  @transport.base_url
113
142
  end
143
+
144
+ # Close SDK-managed idle connections. The client remains usable.
145
+ def close
146
+ @transport.close
147
+ end
148
+
149
+ # A redacted dump: never includes the API key.
150
+ def inspect
151
+ "#<#{self.class.name} account_sid=#{account_sid} base_url=#{base_url}>"
152
+ end
153
+ alias to_s inspect
114
154
  end
115
155
  end
@@ -43,12 +43,25 @@ module VoiceML
43
43
  # HTTP 410 — recording audio is no longer available (no local file, no S3 key).
44
44
  class GoneError < ApiError; end
45
45
 
46
- # HTTP 429 — per-account rate limit exceeded.
47
- class RateLimitError < ApiError; end
46
+ # HTTP 429 — per-account rate limit exceeded. When the server sends a
47
+ # `Retry-After` header, the parsed delay in seconds is exposed on
48
+ # `#retry_after`.
49
+ class RateLimitError < ApiError
50
+ attr_reader :retry_after
51
+
52
+ def retry_after=(seconds)
53
+ @retry_after = seconds
54
+ end
55
+ end
48
56
 
49
57
  # HTTP 501 — endpoint is mounted as a stub (e.g. UserDefinedMessages).
50
58
  class NotImplementedAPIError < ApiError; end
51
59
 
60
+ # The request could not be delivered: connection failures, TLS errors,
61
+ # timeouts, malformed or refused redirects. The original exception is
62
+ # preserved as `#cause`.
63
+ class ConnectionError < ApiError; end
64
+
52
65
  # HTTP 5xx — the server hit an error processing the request.
53
66
  class ServerError < ApiError; end
54
67
 
@@ -6,17 +6,18 @@ module VoiceML
6
6
  # Twilio-compatible Account resource. The voiceml SDK is single-account
7
7
  # (the credential's account is implicit on every call), so this model
8
8
  # exists primarily to decode `GET/POST /Accounts/{Sid}.json` responses
9
- # round-trip with the Twilio shape — useful for migration tooling that
10
- # echoes the account record back to the caller.
9
+ # in the Twilio-compatible wire format — useful for migration tooling
10
+ # that echoes the account record back to the caller.
11
11
  class Account
12
12
  ATTRIBUTES = %w[
13
- sid friendly_name status type auth_token owner_account_sid
13
+ sid friendly_name status type owner_account_sid
14
14
  date_created date_updated subresource_uris uri
15
15
  ].freeze
16
16
 
17
17
  attr_reader(*ATTRIBUTES.map(&:to_sym))
18
18
 
19
19
  def initialize(attrs = {})
20
+ @raw_attributes = attrs
20
21
  ATTRIBUTES.each do |field|
21
22
  value = attrs.key?(field) ? attrs[field] : attrs[field.to_sym]
22
23
  instance_variable_set("@#{field}", value)
@@ -34,11 +35,13 @@ module VoiceML
34
35
  # the account's settlement currency. No `sid` field on the wire (the
35
36
  # balance is account-scoped, not its own resource).
36
37
  class Balance
38
+ include RawAttributes
37
39
  ATTRIBUTES = %w[account_sid balance currency].freeze
38
40
 
39
41
  attr_reader(*ATTRIBUTES.map(&:to_sym))
40
42
 
41
43
  def initialize(attrs = {})
44
+ @raw_attributes = attrs
42
45
  ATTRIBUTES.each do |field|
43
46
  value = attrs.key?(field) ? attrs[field] : attrs[field.to_sym]
44
47
  instance_variable_set("@#{field}", value)
@@ -51,4 +54,45 @@ module VoiceML
51
54
  new(hash)
52
55
  end
53
56
  end
57
+
58
+ # `POST /Accounts/{Sid}/RotateKey` response — the account configuration
59
+ # view plus the freshly generated `api_key`. No read endpoint returns the
60
+ # key, so store it right away; requests signed with the previous key fail
61
+ # with 401 from that point on. The key is secret material and never
62
+ # appears in `inspect` / `to_s` output.
63
+ class AccountWithKey
64
+ include RawAttributes
65
+ ATTRIBUTES = %w[
66
+ sid api_key sip_username name phone_regions allow_cidr disabled
67
+ stt_fs_vendor stt_http_url stt_http_token_set tts_http_url
68
+ tts_http_token_set tts_engine_name tts_default_voice tts_default_language
69
+ stripe_secret_key_set pay_enabled sms_provider sms_allowed_providers
70
+ stt_allowed_providers stt_provider voice_url voice_method
71
+ voice_fallback_url voice_fallback_method aws_bucket aws_region
72
+ aws_prefix aws_access_key_id aws_secret_access_key_set aws_kms_key_id
73
+ created_at updated_at
74
+ ].freeze
75
+
76
+ attr_reader(*ATTRIBUTES.map(&:to_sym))
77
+
78
+ def initialize(attrs = {})
79
+ @raw_attributes = attrs
80
+ ATTRIBUTES.each do |field|
81
+ value = attrs.key?(field) ? attrs[field] : attrs[field.to_sym]
82
+ instance_variable_set("@#{field}", value)
83
+ end
84
+ end
85
+
86
+ def self.from_hash(hash)
87
+ return nil if hash.nil?
88
+
89
+ new(hash)
90
+ end
91
+
92
+ # A redacted dump: never includes the api_key.
93
+ def inspect
94
+ "#<#{self.class.name} sid=#{@sid}>"
95
+ end
96
+ alias to_s inspect
97
+ end
54
98
  end
@@ -5,16 +5,20 @@ require_relative 'common'
5
5
  module VoiceML
6
6
  # Persistent TwiML+callback bundle dispatched by `<Dial><Application>`.
7
7
  class Application
8
+ include RawAttributes
8
9
  ATTRIBUTES = %w[
9
10
  sid account_sid friendly_name api_version voice_url voice_method
10
11
  voice_fallback_url voice_fallback_method voice_caller_id_lookup
11
12
  status_callback status_callback_method status_callback_event
13
+ message_status_callback sms_status_callback
14
+ sms_url sms_method sms_fallback_url sms_fallback_method
12
15
  date_created date_updated uri
13
16
  ].freeze
14
17
 
15
18
  attr_reader(*ATTRIBUTES.map(&:to_sym))
16
19
 
17
20
  def initialize(attrs = {})
21
+ @raw_attributes = attrs
18
22
  ATTRIBUTES.each do |field|
19
23
  value = attrs.key?(field) ? attrs[field] : attrs[field.to_sym]
20
24
  instance_variable_set("@#{field}", value)
@@ -30,11 +34,13 @@ module VoiceML
30
34
 
31
35
  # Paginated `GET /Applications` response.
32
36
  class ApplicationList
37
+ include RawAttributes
33
38
  include Pageable
34
39
 
35
40
  attr_reader :applications
36
41
 
37
42
  def initialize(hash = {})
43
+ @raw_attributes = hash
38
44
  assign_page_fields(hash)
39
45
  @applications = (hash['applications'] || []).map { |a| Application.from_hash(a) }
40
46
  end
@@ -18,21 +18,25 @@ module VoiceML
18
18
 
19
19
  # AssistantsV1Assistant — `aia_asst_...`. Used by list/create/update/delete responses.
20
20
  class AssistantsV1Assistant
21
+ include RawAttributes
21
22
  ATTRIBUTES = %w[
22
23
  account_sid customer_ai id model name owner url personality_prompt
23
24
  date_created date_updated
24
25
  ].freeze
25
26
  attr_reader(*ATTRIBUTES.map(&:to_sym))
26
27
  def initialize(attrs = {})
28
+ @raw_attributes = attrs
27
29
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
28
30
  end
29
31
  def self.from_hash(h); h.nil? ? nil : new(h); end
30
32
  end
31
33
 
32
34
  class AssistantsV1AssistantList
35
+ include RawAttributes
33
36
  include V1Pageable
34
37
  attr_reader :assistants
35
38
  def initialize(hash = {})
39
+ @raw_attributes = hash
36
40
  assign_meta_fields(hash)
37
41
  @assistants = (hash['assistants'] || []).map { |h| AssistantsV1Assistant.from_hash(h) }
38
42
  end
@@ -41,12 +45,14 @@ module VoiceML
41
45
  # AssistantsV1AssistantWithToolsAndKnowledge — fetch-one shape that includes the
42
46
  # attached tools and knowledge arrays inline.
43
47
  class AssistantsV1AssistantWithToolsAndKnowledge
48
+ include RawAttributes
44
49
  ATTRIBUTES = %w[
45
50
  account_sid customer_ai id model name owner url personality_prompt
46
51
  date_created date_updated tools knowledge
47
52
  ].freeze
48
53
  attr_reader(*ATTRIBUTES.map(&:to_sym))
49
54
  def initialize(attrs = {})
55
+ @raw_attributes = attrs
50
56
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
51
57
  end
52
58
  def self.from_hash(h); h.nil? ? nil : new(h); end
@@ -54,21 +60,25 @@ module VoiceML
54
60
 
55
61
  # AssistantsV1Tool — `aia_tool_...`.
56
62
  class AssistantsV1Tool
63
+ include RawAttributes
57
64
  ATTRIBUTES = %w[
58
65
  account_sid description enabled id meta name requires_auth type url
59
66
  date_created date_updated
60
67
  ].freeze
61
68
  attr_reader(*ATTRIBUTES.map(&:to_sym))
62
69
  def initialize(attrs = {})
70
+ @raw_attributes = attrs
63
71
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
64
72
  end
65
73
  def self.from_hash(h); h.nil? ? nil : new(h); end
66
74
  end
67
75
 
68
76
  class AssistantsV1ToolList
77
+ include RawAttributes
69
78
  include V1Pageable
70
79
  attr_reader :tools
71
80
  def initialize(hash = {})
81
+ @raw_attributes = hash
72
82
  assign_meta_fields(hash)
73
83
  @tools = (hash['tools'] || []).map { |h| AssistantsV1Tool.from_hash(h) }
74
84
  end
@@ -76,12 +86,14 @@ module VoiceML
76
86
 
77
87
  # AssistantsV1ToolWithPolicies — fetch-one shape that includes the policies array inline.
78
88
  class AssistantsV1ToolWithPolicies
89
+ include RawAttributes
79
90
  ATTRIBUTES = %w[
80
91
  account_sid description enabled id meta name requires_auth type url
81
92
  date_created date_updated policies
82
93
  ].freeze
83
94
  attr_reader(*ATTRIBUTES.map(&:to_sym))
84
95
  def initialize(attrs = {})
96
+ @raw_attributes = attrs
85
97
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
86
98
  end
87
99
  def self.from_hash(h); h.nil? ? nil : new(h); end
@@ -89,21 +101,25 @@ module VoiceML
89
101
 
90
102
  # AssistantsV1Knowledge — `aia_know_...`.
91
103
  class AssistantsV1Knowledge
104
+ include RawAttributes
92
105
  ATTRIBUTES = %w[
93
106
  account_sid description id knowledge_source_details name status type url
94
107
  embedding_model date_created date_updated
95
108
  ].freeze
96
109
  attr_reader(*ATTRIBUTES.map(&:to_sym))
97
110
  def initialize(attrs = {})
111
+ @raw_attributes = attrs
98
112
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
99
113
  end
100
114
  def self.from_hash(h); h.nil? ? nil : new(h); end
101
115
  end
102
116
 
103
117
  class AssistantsV1KnowledgeList
118
+ include RawAttributes
104
119
  include V1Pageable
105
120
  attr_reader :knowledge
106
121
  def initialize(hash = {})
122
+ @raw_attributes = hash
107
123
  assign_meta_fields(hash)
108
124
  @knowledge = (hash['knowledge'] || []).map { |h| AssistantsV1Knowledge.from_hash(h) }
109
125
  end
@@ -111,9 +127,11 @@ module VoiceML
111
127
 
112
128
  # AssistantsV1KnowledgeStatus — read-only ingestion status snapshot.
113
129
  class AssistantsV1KnowledgeStatus
130
+ include RawAttributes
114
131
  ATTRIBUTES = %w[account_sid status last_status date_updated].freeze
115
132
  attr_reader(*ATTRIBUTES.map(&:to_sym))
116
133
  def initialize(attrs = {})
134
+ @raw_attributes = attrs
117
135
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
118
136
  end
119
137
  def self.from_hash(h); h.nil? ? nil : new(h); end
@@ -121,18 +139,22 @@ module VoiceML
121
139
 
122
140
  # AssistantsV1KnowledgeChunk — single retrieved chunk of indexed Knowledge content.
123
141
  class AssistantsV1KnowledgeChunk
142
+ include RawAttributes
124
143
  ATTRIBUTES = %w[account_sid content metadata date_created date_updated].freeze
125
144
  attr_reader(*ATTRIBUTES.map(&:to_sym))
126
145
  def initialize(attrs = {})
146
+ @raw_attributes = attrs
127
147
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
128
148
  end
129
149
  def self.from_hash(h); h.nil? ? nil : new(h); end
130
150
  end
131
151
 
132
152
  class AssistantsV1KnowledgeChunkList
153
+ include RawAttributes
133
154
  include V1Pageable
134
155
  attr_reader :chunks
135
156
  def initialize(hash = {})
157
+ @raw_attributes = hash
136
158
  assign_meta_fields(hash)
137
159
  @chunks = (hash['chunks'] || []).map { |h| AssistantsV1KnowledgeChunk.from_hash(h) }
138
160
  end
@@ -140,20 +162,24 @@ module VoiceML
140
162
 
141
163
  # AssistantsV1Session — a chat session between an identity and an Assistant.
142
164
  class AssistantsV1Session
165
+ include RawAttributes
143
166
  ATTRIBUTES = %w[
144
167
  id account_sid assistant_id verified identity date_created date_updated
145
168
  ].freeze
146
169
  attr_reader(*ATTRIBUTES.map(&:to_sym))
147
170
  def initialize(attrs = {})
171
+ @raw_attributes = attrs
148
172
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
149
173
  end
150
174
  def self.from_hash(h); h.nil? ? nil : new(h); end
151
175
  end
152
176
 
153
177
  class AssistantsV1SessionList
178
+ include RawAttributes
154
179
  include V1Pageable
155
180
  attr_reader :sessions
156
181
  def initialize(hash = {})
182
+ @raw_attributes = hash
157
183
  assign_meta_fields(hash)
158
184
  @sessions = (hash['sessions'] || []).map { |h| AssistantsV1Session.from_hash(h) }
159
185
  end
@@ -161,21 +187,25 @@ module VoiceML
161
187
 
162
188
  # AssistantsV1Message — `aia_msg_...`. One message in an Assistant session.
163
189
  class AssistantsV1Message
190
+ include RawAttributes
164
191
  ATTRIBUTES = %w[
165
192
  id account_sid assistant_id session_id identity role content meta
166
193
  date_created date_updated
167
194
  ].freeze
168
195
  attr_reader(*ATTRIBUTES.map(&:to_sym))
169
196
  def initialize(attrs = {})
197
+ @raw_attributes = attrs
170
198
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
171
199
  end
172
200
  def self.from_hash(h); h.nil? ? nil : new(h); end
173
201
  end
174
202
 
175
203
  class AssistantsV1MessageList
204
+ include RawAttributes
176
205
  include V1Pageable
177
206
  attr_reader :messages
178
207
  def initialize(hash = {})
208
+ @raw_attributes = hash
179
209
  assign_meta_fields(hash)
180
210
  @messages = (hash['messages'] || []).map { |h| AssistantsV1Message.from_hash(h) }
181
211
  end
@@ -184,9 +214,11 @@ module VoiceML
184
214
  # AssistantsV1SendMessageResponse — POST /v1/Assistants/{id}/Messages return shape.
185
215
  # Distinct from AssistantsV1Message: carries the model's reply body + flag/abort signals.
186
216
  class AssistantsV1SendMessageResponse
217
+ include RawAttributes
187
218
  ATTRIBUTES = %w[status flagged aborted session_id account_sid body error].freeze
188
219
  attr_reader(*ATTRIBUTES.map(&:to_sym))
189
220
  def initialize(attrs = {})
221
+ @raw_attributes = attrs
190
222
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
191
223
  end
192
224
  def self.from_hash(h); h.nil? ? nil : new(h); end
@@ -194,21 +226,25 @@ module VoiceML
194
226
 
195
227
  # AssistantsV1Feedback — `aia_fdbk_...`. Numeric score + text feedback on a message.
196
228
  class AssistantsV1Feedback
229
+ include RawAttributes
197
230
  ATTRIBUTES = %w[
198
231
  assistant_id id account_sid user_sid message_id score session_id text
199
232
  date_created date_updated
200
233
  ].freeze
201
234
  attr_reader(*ATTRIBUTES.map(&:to_sym))
202
235
  def initialize(attrs = {})
236
+ @raw_attributes = attrs
203
237
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
204
238
  end
205
239
  def self.from_hash(h); h.nil? ? nil : new(h); end
206
240
  end
207
241
 
208
242
  class AssistantsV1FeedbackList
243
+ include RawAttributes
209
244
  include V1Pageable
210
245
  attr_reader :feedbacks
211
246
  def initialize(hash = {})
247
+ @raw_attributes = hash
212
248
  assign_meta_fields(hash)
213
249
  @feedbacks = (hash['feedbacks'] || []).map { |h| AssistantsV1Feedback.from_hash(h) }
214
250
  end
@@ -217,21 +253,25 @@ module VoiceML
217
253
  # AssistantsV1Policy — `aia_plcy_...`. Read-only — listed under /v1/Policies (filterable
218
254
  # by ToolId/KnowledgeId) and embedded inline on Tool fetch responses.
219
255
  class AssistantsV1Policy
256
+ include RawAttributes
220
257
  ATTRIBUTES = %w[
221
258
  id name description account_sid user_sid type policy_details
222
259
  date_created date_updated
223
260
  ].freeze
224
261
  attr_reader(*ATTRIBUTES.map(&:to_sym))
225
262
  def initialize(attrs = {})
263
+ @raw_attributes = attrs
226
264
  ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
227
265
  end
228
266
  def self.from_hash(h); h.nil? ? nil : new(h); end
229
267
  end
230
268
 
231
269
  class AssistantsV1PolicyList
270
+ include RawAttributes
232
271
  include V1Pageable
233
272
  attr_reader :policies
234
273
  def initialize(hash = {})
274
+ @raw_attributes = hash
235
275
  assign_meta_fields(hash)
236
276
  @policies = (hash['policies'] || []).map { |h| AssistantsV1Policy.from_hash(h) }
237
277
  end
@@ -6,6 +6,7 @@ module VoiceML
6
6
  # A Twilio-compatible Call resource. Returned by `client.calls.create`, `client.calls.get`,
7
7
  # `client.calls.update`, and listed in `CallList#calls`.
8
8
  class Call
9
+ include RawAttributes
9
10
  ATTRIBUTES = %w[
10
11
  sid account_sid api_version to to_formatted from from_formatted
11
12
  parent_call_sid caller_name forwarded_from status direction
@@ -17,6 +18,7 @@ module VoiceML
17
18
  attr_reader(*ATTRIBUTES.map(&:to_sym))
18
19
 
19
20
  def initialize(attrs = {})
21
+ @raw_attributes = attrs
20
22
  ATTRIBUTES.each do |field|
21
23
  value = attrs.key?(field) ? attrs[field] : attrs[field.to_sym]
22
24
  instance_variable_set("@#{field}", value)
@@ -32,11 +34,13 @@ module VoiceML
32
34
 
33
35
  # Paginated `GET /Calls` response.
34
36
  class CallList
37
+ include RawAttributes
35
38
  include Pageable
36
39
 
37
40
  attr_reader :calls
38
41
 
39
42
  def initialize(hash = {})
43
+ @raw_attributes = hash
40
44
  assign_page_fields(hash)
41
45
  @calls = (hash['calls'] || []).map { |c| Call.from_hash(c) }
42
46
  end