elevenlabs_client 0.6.0 → 0.8.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +220 -0
  3. data/README.md +181 -58
  4. data/lib/elevenlabs_client/client.rb +110 -269
  5. data/lib/elevenlabs_client/configuration.rb +119 -0
  6. data/lib/elevenlabs_client/endpoints/admin/history.rb +1 -1
  7. data/lib/elevenlabs_client/endpoints/admin/pronunciation_dictionaries.rb +103 -0
  8. data/lib/elevenlabs_client/endpoints/admin/samples.rb +30 -0
  9. data/lib/elevenlabs_client/endpoints/admin/service_account_api_keys.rb +77 -0
  10. data/lib/elevenlabs_client/endpoints/admin/service_accounts.rb +29 -0
  11. data/lib/elevenlabs_client/endpoints/admin/user.rb +12 -0
  12. data/lib/elevenlabs_client/endpoints/admin/webhooks.rb +33 -0
  13. data/lib/elevenlabs_client/endpoints/admin/workspace_groups.rb +56 -0
  14. data/lib/elevenlabs_client/endpoints/admin/workspace_invites.rb +52 -0
  15. data/lib/elevenlabs_client/endpoints/admin/workspace_members.rb +31 -0
  16. data/lib/elevenlabs_client/endpoints/admin/workspace_resources.rb +53 -0
  17. data/lib/elevenlabs_client/endpoints/admin/workspace_webhooks.rb +30 -0
  18. data/lib/elevenlabs_client/endpoints/agents_platform/agents.rb +165 -0
  19. data/lib/elevenlabs_client/endpoints/agents_platform/batch_calling.rb +89 -0
  20. data/lib/elevenlabs_client/endpoints/agents_platform/conversations.rb +121 -0
  21. data/lib/elevenlabs_client/endpoints/agents_platform/knowledge_base.rb +234 -0
  22. data/lib/elevenlabs_client/endpoints/agents_platform/llm_usage.rb +50 -0
  23. data/lib/elevenlabs_client/endpoints/agents_platform/mcp_servers.rb +139 -0
  24. data/lib/elevenlabs_client/endpoints/agents_platform/outbound_calling.rb +55 -0
  25. data/lib/elevenlabs_client/endpoints/agents_platform/phone_numbers.rb +86 -0
  26. data/lib/elevenlabs_client/endpoints/agents_platform/test_invocations.rb +44 -0
  27. data/lib/elevenlabs_client/endpoints/agents_platform/tests.rb +138 -0
  28. data/lib/elevenlabs_client/endpoints/agents_platform/tools.rb +107 -0
  29. data/lib/elevenlabs_client/endpoints/agents_platform/widgets.rb +52 -0
  30. data/lib/elevenlabs_client/endpoints/agents_platform/workspace.rb +130 -0
  31. data/lib/elevenlabs_client/errors.rb +4 -0
  32. data/lib/elevenlabs_client/http_client.rb +325 -0
  33. data/lib/elevenlabs_client/version.rb +1 -1
  34. data/lib/elevenlabs_client.rb +99 -15
  35. metadata +27 -2
@@ -1,29 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "faraday"
4
- require "faraday/multipart"
5
- require "json"
6
-
7
3
  module ElevenlabsClient
4
+ # Main client class for the ElevenLabs API
5
+ # Refactored for better maintainability and architecture
8
6
  class Client
9
- DEFAULT_BASE_URL = "https://api.elevenlabs.io"
7
+ attr_reader :http_client
8
+
9
+ def initialize(api_key: nil, base_url: nil, **options)
10
+ # Create configuration
11
+ configuration = Configuration.new
12
+ configuration.api_key = api_key if api_key
13
+ configuration.base_url = base_url if base_url
14
+
15
+ # Apply any additional options
16
+ options.each do |key, value|
17
+ configuration.public_send("#{key}=", value) if configuration.respond_to?("#{key}=")
18
+ end
10
19
 
11
- attr_reader :base_url, :api_key, :dubs, :text_to_speech, :text_to_dialogue, :sound_generation, :text_to_voice, :models, :voices, :music, :audio_isolation, :audio_native, :forced_alignment, :speech_to_speech, :speech_to_text, :websocket_text_to_speech, :history, :usage, :user, :voice_library
20
+ # Validate configuration
21
+ configuration.validate!
12
22
 
13
- def initialize(api_key: nil, base_url: nil, api_key_env: "ELEVENLABS_API_KEY", base_url_env: "ELEVENLABS_BASE_URL")
14
- @api_key = api_key || fetch_api_key(api_key_env)
15
- @base_url = base_url || fetch_base_url(base_url_env)
16
- @conn = build_connection
23
+ # Create HTTP client
24
+ @http_client = HttpClient.new(
25
+ api_key: configuration.resolved_api_key,
26
+ base_url: configuration.resolved_base_url
27
+ )
28
+
29
+ # Initialize endpoints directly for now (simpler approach)
30
+ initialize_endpoints
31
+ end
32
+
33
+ private
34
+
35
+ def initialize_endpoints
36
+ # Core TTS/Audio endpoints
17
37
  @dubs = Dubs.new(self)
18
38
  @text_to_speech = TextToSpeech.new(self)
19
39
  @text_to_dialogue = TextToDialogue.new(self)
20
40
  @sound_generation = SoundGeneration.new(self)
21
41
  @text_to_voice = TextToVoice.new(self)
22
- @models = Admin::Models.new(self)
23
- @history = Admin::History.new(self)
24
- @usage = Admin::Usage.new(self)
25
- @user = Admin::User.new(self)
26
- @voice_library = Admin::VoiceLibrary.new(self)
27
42
  @voices = Voices.new(self)
28
43
  @music = Endpoints::Music.new(self)
29
44
  @audio_isolation = AudioIsolation.new(self)
@@ -32,306 +47,132 @@ module ElevenlabsClient
32
47
  @speech_to_speech = SpeechToSpeech.new(self)
33
48
  @speech_to_text = SpeechToText.new(self)
34
49
  @websocket_text_to_speech = WebSocketTextToSpeech.new(self)
35
- end
36
50
 
37
- # Makes an authenticated GET request
38
- # @param path [String] API endpoint path
39
- # @param params [Hash] Query parameters
40
- # @return [Hash] Response body
51
+ # Admin endpoints
52
+ @models = Admin::Models.new(self)
53
+ @history = Admin::History.new(self)
54
+ @usage = Admin::Usage.new(self)
55
+ @user = Admin::User.new(self)
56
+ @voice_library = Admin::VoiceLibrary.new(self)
57
+ @samples = Admin::Samples.new(self)
58
+ @pronunciation_dictionaries = Admin::PronunciationDictionaries.new(self)
59
+ @service_accounts = Admin::ServiceAccounts.new(self)
60
+ @webhooks = Admin::Webhooks.new(self)
61
+ @workspace_groups = Admin::WorkspaceGroups.new(self)
62
+ @workspace_invites = Admin::WorkspaceInvites.new(self)
63
+ @workspace_members = Admin::WorkspaceMembers.new(self)
64
+ @workspace_resources = Admin::WorkspaceResources.new(self)
65
+ @workspace_webhooks = Admin::WorkspaceWebhooks.new(self)
66
+ @service_account_api_keys = Admin::ServiceAccountApiKeys.new(self)
67
+
68
+ # Agents Platform endpoints
69
+ @agents = Endpoints::AgentsPlatform::Agents.new(self)
70
+ @conversations = Endpoints::AgentsPlatform::Conversations.new(self)
71
+ @tools = Endpoints::AgentsPlatform::Tools.new(self)
72
+ @knowledge_base = Endpoints::AgentsPlatform::KnowledgeBase.new(self)
73
+ @tests = Endpoints::AgentsPlatform::Tests.new(self)
74
+ @test_invocations = Endpoints::AgentsPlatform::TestInvocations.new(self)
75
+ @phone_numbers = Endpoints::AgentsPlatform::PhoneNumbers.new(self)
76
+ @widgets = Endpoints::AgentsPlatform::Widgets.new(self)
77
+ @outbound_calling = Endpoints::AgentsPlatform::OutboundCalling.new(self)
78
+ @batch_calling = Endpoints::AgentsPlatform::BatchCalling.new(self)
79
+ @workspace = Endpoints::AgentsPlatform::Workspace.new(self)
80
+ @llm_usage = Endpoints::AgentsPlatform::LlmUsage.new(self)
81
+ @mcp_servers = Endpoints::AgentsPlatform::McpServers.new(self)
82
+ end
83
+
84
+ public
85
+
86
+ # Endpoint accessors
87
+ attr_reader :dubs, :text_to_speech, :text_to_dialogue, :sound_generation, :text_to_voice,
88
+ :voices, :music, :audio_isolation, :audio_native, :forced_alignment,
89
+ :speech_to_speech, :speech_to_text, :websocket_text_to_speech,
90
+ :models, :history, :usage, :user, :voice_library, :samples,
91
+ :pronunciation_dictionaries, :service_accounts, :webhooks,
92
+ :workspace_groups, :workspace_invites, :workspace_members, :workspace_resources,
93
+ :workspace_webhooks, :service_account_api_keys,
94
+ :agents, :conversations, :tools, :knowledge_base, :tests, :test_invocations,
95
+ :phone_numbers, :widgets, :outbound_calling, :batch_calling, :workspace,
96
+ :llm_usage, :mcp_servers
97
+
98
+ # Delegate HTTP methods to http_client for backward compatibility
41
99
  def get(path, params = {})
42
- response = @conn.get(path, params) do |req|
43
- req.headers["xi-api-key"] = api_key
44
- end
45
-
46
- handle_response(response)
100
+ http_client.get(path, params)
47
101
  end
48
102
 
49
- # Makes an authenticated POST request
50
- # @param path [String] API endpoint path
51
- # @param body [Hash, nil] Request body
52
- # @return [Hash] Response body
53
103
  def post(path, body = nil)
54
- response = @conn.post(path) do |req|
55
- req.headers["xi-api-key"] = api_key
56
- req.headers["Content-Type"] = "application/json"
57
- req.body = body.to_json if body
58
- end
104
+ http_client.post(path, body)
105
+ end
59
106
 
60
- handle_response(response)
107
+ def patch(path, body = nil)
108
+ http_client.patch(path, body)
61
109
  end
62
110
 
63
- # Makes an authenticated DELETE request
64
- # @param path [String] API endpoint path
65
- # @return [Hash] Response body
66
111
  def delete(path)
67
- response = @conn.delete(path) do |req|
68
- req.headers["xi-api-key"] = api_key
69
- end
70
-
71
- handle_response(response)
112
+ http_client.delete(path)
72
113
  end
73
114
 
74
- # Makes an authenticated PATCH request
75
- # @param path [String] API endpoint path
76
- # @param body [Hash, nil] Request body
77
- # @return [Hash] Response body
78
- def patch(path, body = nil)
79
- response = @conn.patch(path) do |req|
80
- req.headers["xi-api-key"] = api_key
81
- req.headers["Content-Type"] = "application/json"
82
- req.body = body.to_json if body
83
- end
84
-
85
- handle_response(response)
115
+ def delete_with_body(path, body = nil)
116
+ http_client.delete_with_body(path, body)
86
117
  end
87
118
 
88
- # Makes an authenticated multipart POST request
89
- # @param path [String] API endpoint path
90
- # @param payload [Hash] Multipart payload
91
- # @return [Hash] Response body
92
119
  def post_multipart(path, payload)
93
- response = @conn.post(path) do |req|
94
- req.headers["xi-api-key"] = api_key
95
- req.body = payload
96
- end
97
-
98
- handle_response(response)
120
+ http_client.post_multipart(path, payload)
99
121
  end
100
122
 
101
- # Makes an authenticated GET request expecting binary response
102
- # @param path [String] API endpoint path
103
- # @return [String] Binary response body
104
123
  def get_binary(path)
105
- response = @conn.get(path) do |req|
106
- req.headers["xi-api-key"] = api_key
107
- end
108
-
109
- handle_response(response)
124
+ http_client.get_binary(path)
110
125
  end
111
126
 
112
- # Makes an authenticated POST request expecting binary response
113
- # @param path [String] API endpoint path
114
- # @param body [Hash, nil] Request body
115
- # @return [String] Binary response body
116
127
  def post_binary(path, body = nil)
117
- response = @conn.post(path) do |req|
118
- req.headers["xi-api-key"] = api_key
119
- req.headers["Content-Type"] = "application/json"
120
- req.body = body.to_json if body
121
- end
122
-
123
- handle_response(response)
128
+ http_client.post_binary(path, body)
124
129
  end
125
130
 
126
- # Makes an authenticated POST request with custom headers
127
- # @param path [String] API endpoint path
128
- # @param body [Hash, nil] Request body
129
- # @param custom_headers [Hash] Additional headers
130
- # @return [String] Response body (binary or text)
131
131
  def post_with_custom_headers(path, body = nil, custom_headers = {})
132
- response = @conn.post(path) do |req|
133
- req.headers["xi-api-key"] = api_key
134
- req.headers["Content-Type"] = "application/json"
135
- custom_headers.each { |key, value| req.headers[key] = value }
136
- req.body = body.to_json if body
137
- end
138
-
139
- # For streaming/binary responses, return raw body
140
- if custom_headers["Accept"]&.include?("audio") || custom_headers["Transfer-Encoding"] == "chunked"
141
- handle_response(response)
142
- else
143
- handle_response(response)
144
- end
132
+ http_client.post_with_custom_headers(path, body, custom_headers)
145
133
  end
146
134
 
147
- # Makes an authenticated POST request with streaming response
148
- # @param path [String] API endpoint path
149
- # @param body [Hash, nil] Request body
150
- # @param block [Proc] Block to handle each chunk
151
- # @return [Faraday::Response] Response object
152
135
  def post_streaming(path, body = nil, &block)
153
- response = @conn.post(path) do |req|
154
- req.headers["xi-api-key"] = api_key
155
- req.headers["Content-Type"] = "application/json"
156
- req.headers["Accept"] = "audio/mpeg"
157
- req.body = body.to_json if body
158
-
159
- # Set up streaming callback
160
- req.options.on_data = proc do |chunk, _|
161
- block.call(chunk) if block_given?
162
- end
163
- end
164
-
165
- handle_response(response)
136
+ http_client.post_streaming(path, body, &block)
166
137
  end
167
138
 
168
- # Makes an authenticated GET request with streaming response
169
- # @param path [String] API endpoint path
170
- # @param block [Proc] Block to handle each chunk
171
- # @return [Faraday::Response] Response object
172
139
  def get_streaming(path, &block)
173
- response = @conn.get(path) do |req|
174
- req.headers["xi-api-key"] = api_key
175
- req.headers["Accept"] = "audio/mpeg"
176
-
177
- # Set up streaming callback
178
- req.options.on_data = proc do |chunk, _|
179
- block.call(chunk) if block_given?
180
- end
181
- end
182
-
183
- handle_response(response)
140
+ http_client.get_streaming(path, &block)
184
141
  end
185
142
 
186
- # Makes an authenticated POST request with streaming response for timestamp data
187
- # @param path [String] API endpoint path
188
- # @param body [Hash, nil] Request body
189
- # @param block [Proc] Block to handle each JSON chunk with timestamps
190
- # @return [Faraday::Response] Response object
191
143
  def post_streaming_with_timestamps(path, body = nil, &block)
192
- buffer = ""
193
-
194
- response = @conn.post(path) do |req|
195
- req.headers["xi-api-key"] = api_key
196
- req.headers["Content-Type"] = "application/json"
197
- req.body = body.to_json if body
198
-
199
- # Set up streaming callback for JSON chunks
200
- req.options.on_data = proc do |chunk, _|
201
- if block_given?
202
- buffer += chunk
203
-
204
- # Process complete JSON objects
205
- while buffer.include?("\n")
206
- line, buffer = buffer.split("\n", 2)
207
- next if line.strip.empty?
208
-
209
- begin
210
- json_data = JSON.parse(line)
211
- block.call(json_data)
212
- rescue JSON::ParserError
213
- # Skip malformed JSON lines
214
- next
215
- end
216
- end
217
- end
218
- end
219
- end
220
-
221
- handle_response(response)
144
+ http_client.post_streaming_with_timestamps(path, body, &block)
222
145
  end
223
146
 
224
- # Helper method to create Faraday::Multipart::FilePart
225
- # @param file_io [IO] File IO object
226
- # @param filename [String] Original filename
227
- # @return [Faraday::Multipart::FilePart]
228
147
  def file_part(file_io, filename)
229
- Faraday::Multipart::FilePart.new(file_io, mime_for(filename), filename)
148
+ http_client.file_part(file_io, filename)
230
149
  end
231
150
 
232
- private
233
-
234
- def fetch_api_key(env_key = "ELEVENLABS_API_KEY")
235
- # First try Settings, then ENV, then raise error
236
- if Settings.properties&.dig(:elevenlabs_api_key)
237
- Settings.elevenlabs_api_key
238
- else
239
- ENV.fetch(env_key) do
240
- raise AuthenticationError, "#{env_key} environment variable is required but not set and Settings.properties[:elevenlabs_api_key] is not configured"
241
- end
242
- end
151
+ # Convenience accessors for backward compatibility
152
+ def base_url
153
+ http_client.base_url
243
154
  end
244
155
 
245
- def fetch_base_url(env_key = "ELEVENLABS_BASE_URL")
246
- # First try Settings, then ENV, then default
247
- if Settings.properties&.dig(:elevenlabs_base_uri)
248
- Settings.elevenlabs_base_uri
249
- else
250
- ENV.fetch(env_key, DEFAULT_BASE_URL)
251
- end
156
+ def api_key
157
+ http_client.api_key
252
158
  end
253
159
 
254
- def build_connection
255
- Faraday.new(url: base_url) do |f|
256
- f.request :multipart
257
- f.request :url_encoded
258
- f.response :json, content_type: /\bjson$/
259
- f.adapter Faraday.default_adapter
260
- end
160
+ # Debug information
161
+ def inspect
162
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
163
+ "base_url=#{base_url.inspect}>"
261
164
  end
262
165
 
263
- def handle_response(response)
264
- case response.status
265
- when 200..299
266
- response.body
267
- when 400
268
- error_message = extract_error_message(response.body)
269
- raise BadRequestError, error_message.empty? ? "Bad request - invalid parameters" : error_message
270
- when 401
271
- error_message = extract_error_message(response.body)
272
- raise AuthenticationError, error_message.empty? ? "Invalid API key or authentication failed" : error_message
273
- when 404
274
- error_message = extract_error_message(response.body)
275
- raise NotFoundError, error_message.empty? ? "Resource not found" : error_message
276
- when 422
277
- error_message = extract_error_message(response.body)
278
- raise UnprocessableEntityError, error_message.empty? ? "Unprocessable entity - invalid data" : error_message
279
- when 429
280
- error_message = extract_error_message(response.body)
281
- raise RateLimitError, error_message.empty? ? "Rate limit exceeded" : error_message
282
- when 400..499
283
- error_message = extract_error_message(response.body)
284
- raise ValidationError, error_message.empty? ? "Client error occurred with status #{response.status}" : error_message
285
- else
286
- error_message = extract_error_message(response.body)
287
- raise APIError, error_message.empty? ? "API request failed with status #{response.status}" : error_message
288
- end
289
- end
290
-
291
- private
292
-
293
- def extract_error_message(response_body)
294
- return "" if response_body.nil? || response_body.empty?
295
-
296
- # Handle non-string response bodies
297
- body_str = response_body.is_a?(String) ? response_body : response_body.to_s
298
-
166
+ # Health check method
167
+ def health_check
299
168
  begin
300
- error_info = JSON.parse(body_str)
301
-
302
- # Try different common error message fields
303
- message = error_info["detail"] ||
304
- error_info["message"] ||
305
- error_info["error"] ||
306
- error_info["errors"]
307
-
308
- # Handle nested detail objects
309
- if message.is_a?(Hash)
310
- message = message["message"] || message.to_s
311
- elsif message.is_a?(Array)
312
- message = message.first.to_s
313
- end
314
-
315
- message.to_s
316
- rescue JSON::ParserError, TypeError
317
- # If not JSON or can't be parsed, return the raw body (truncated if too long)
318
- body_str.length > 200 ? "#{body_str[0..200]}..." : body_str
169
+ # Try a simple API call to verify connectivity
170
+ get("/v1/models")
171
+ { status: :ok, message: "Client is healthy" }
172
+ rescue => e
173
+ { status: :error, message: e.message, error_class: e.class.name }
319
174
  end
320
175
  end
321
176
 
322
- def mime_for(filename)
323
- ext = File.extname(filename).downcase
324
- case ext
325
- when ".mp4" then "video/mp4"
326
- when ".mov" then "video/quicktime"
327
- when ".avi" then "video/x-msvideo"
328
- when ".mkv" then "video/x-matroska"
329
- when ".mp3" then "audio/mpeg"
330
- when ".wav" then "audio/wav"
331
- when ".flac" then "audio/flac"
332
- when ".m4a" then "audio/mp4"
333
- else "application/octet-stream"
334
- end
335
- end
336
177
  end
337
178
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ # Configuration management for the ElevenLabs client
5
+ class Configuration
6
+ DEFAULT_BASE_URL = "https://api.elevenlabs.io"
7
+ DEFAULT_API_KEY_ENV = "ELEVENLABS_API_KEY"
8
+ DEFAULT_BASE_URL_ENV = "ELEVENLABS_BASE_URL"
9
+
10
+ attr_accessor :api_key, :base_url, :api_key_env, :base_url_env, :timeout, :open_timeout,
11
+ :retry_count, :retry_delay, :user_agent, :logger, :log_level
12
+
13
+ def initialize
14
+ @api_key = nil
15
+ @base_url = DEFAULT_BASE_URL
16
+ @api_key_env = DEFAULT_API_KEY_ENV
17
+ @base_url_env = DEFAULT_BASE_URL_ENV
18
+ @timeout = 30
19
+ @open_timeout = 10
20
+ @retry_count = 3
21
+ @retry_delay = 1
22
+ @user_agent = "ElevenlabsClient/#{ElevenlabsClient::VERSION}"
23
+ @logger = nil
24
+ @log_level = :info
25
+ end
26
+
27
+ # Get API key from configuration, environment, or Settings
28
+ def resolved_api_key
29
+ return @api_key if @api_key
30
+
31
+ # Try Settings first
32
+ if Settings.properties&.dig(:elevenlabs_api_key)
33
+ return Settings.elevenlabs_api_key
34
+ end
35
+
36
+ # Then try environment variable
37
+ env_key = ENV.fetch(@api_key_env) do
38
+ raise AuthenticationError,
39
+ "#{@api_key_env} environment variable is required but not set and " \
40
+ "Settings.properties[:elevenlabs_api_key] is not configured"
41
+ end
42
+
43
+ env_key
44
+ end
45
+
46
+ # Get base URL from configuration, environment, or Settings
47
+ def resolved_base_url
48
+ return @base_url if @base_url != DEFAULT_BASE_URL
49
+
50
+ # Try Settings first
51
+ if Settings.properties&.dig(:elevenlabs_base_uri)
52
+ return Settings.elevenlabs_base_uri
53
+ end
54
+
55
+ # Then try environment variable, with default fallback
56
+ ENV.fetch(@base_url_env, DEFAULT_BASE_URL)
57
+ end
58
+
59
+ # Validate configuration
60
+ def validate!
61
+ resolved_api_key # This will raise if API key is missing
62
+
63
+ unless resolved_base_url.match?(/\Ahttps?:\/\//)
64
+ raise ArgumentError, "Invalid base URL: #{resolved_base_url}"
65
+ end
66
+
67
+ unless timeout.is_a?(Numeric) && timeout > 0
68
+ raise ArgumentError, "Timeout must be a positive number"
69
+ end
70
+
71
+ unless retry_count.is_a?(Integer) && retry_count >= 0
72
+ raise ArgumentError, "Retry count must be a non-negative integer"
73
+ end
74
+ end
75
+
76
+ # Create a hash representation of the configuration
77
+ def to_h
78
+ {
79
+ api_key: api_key ? "[REDACTED]" : nil,
80
+ base_url: resolved_base_url,
81
+ timeout: timeout,
82
+ open_timeout: open_timeout,
83
+ retry_count: retry_count,
84
+ retry_delay: retry_delay,
85
+ user_agent: user_agent,
86
+ log_level: log_level
87
+ }
88
+ end
89
+
90
+ # Reset configuration to defaults
91
+ def reset!
92
+ initialize
93
+ end
94
+ end
95
+
96
+ class << self
97
+ # Global configuration instance
98
+ def configuration
99
+ @configuration ||= Configuration.new
100
+ end
101
+
102
+ # Configure the client globally
103
+ def configure
104
+ yield(configuration) if block_given?
105
+ configuration.validate!
106
+ configuration
107
+ end
108
+
109
+ # Reset global configuration
110
+ def reset_configuration!
111
+ @configuration = Configuration.new
112
+ end
113
+
114
+ # Convenience method to get configured client
115
+ def client
116
+ Client.new
117
+ end
118
+ end
119
+ end
@@ -22,7 +22,7 @@ module ElevenlabsClient
22
22
  # @return [Hash] Response containing history items, pagination info
23
23
  def list(**options)
24
24
  endpoint = "/v1/history"
25
-
25
+
26
26
  # Build query parameters
27
27
  query_params = {}
28
28
  query_params[:page_size] = options[:page_size] if options[:page_size]
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ module Admin
5
+ class PronunciationDictionaries
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ # POST /v1/pronunciation-dictionaries/add-from-file (multipart)
11
+ # Creates a new pronunciation dictionary from a lexicon .PLS file
12
+ # Required: name
13
+ # Optional: file (IO + filename), description, workspace_access
14
+ def add_from_file(name:, file_io: nil, filename: nil, description: nil, workspace_access: nil)
15
+ raise ArgumentError, "name is required" if name.nil? || name.to_s.strip.empty?
16
+
17
+ endpoint = "/v1/pronunciation-dictionaries/add-from-file"
18
+ payload = { name: name }
19
+ payload[:description] = description if description
20
+ payload[:workspace_access] = workspace_access if workspace_access
21
+
22
+ if file_io && filename
23
+ payload[:file] = @client.file_part(file_io, filename)
24
+ end
25
+
26
+ @client.post_multipart(endpoint, payload)
27
+ end
28
+
29
+ alias_method :create_from_file, :add_from_file
30
+
31
+ # POST /v1/pronunciation-dictionaries/add-from-rules (json)
32
+ # Creates a new pronunciation dictionary from provided rules
33
+ # Required: name, rules (Array)
34
+ # Optional: description, workspace_access
35
+ def add_from_rules(name:, rules:, description: nil, workspace_access: nil)
36
+ raise ArgumentError, "name is required" if name.nil? || name.to_s.strip.empty?
37
+ raise ArgumentError, "rules must be a non-empty Array" unless rules.is_a?(Array) && !rules.empty?
38
+
39
+ endpoint = "/v1/pronunciation-dictionaries/add-from-rules"
40
+ body = { name: name, rules: rules }
41
+ body[:description] = description if description
42
+ body[:workspace_access] = workspace_access if workspace_access
43
+
44
+ @client.post(endpoint, body)
45
+ end
46
+
47
+ alias_method :create_from_rules, :add_from_rules
48
+
49
+ # GET /v1/pronunciation-dictionaries/:pronunciation_dictionary_id
50
+ def get_pronunciation_dictionary(pronunciation_dictionary_id)
51
+ raise ArgumentError, "pronunciation_dictionary_id is required" if pronunciation_dictionary_id.nil? || pronunciation_dictionary_id.to_s.strip.empty?
52
+
53
+ endpoint = "/v1/pronunciation-dictionaries/#{pronunciation_dictionary_id}"
54
+ @client.get(endpoint)
55
+ end
56
+
57
+ alias_method :get, :get_pronunciation_dictionary
58
+
59
+ # PATCH /v1/pronunciation-dictionaries/:pronunciation_dictionary_id
60
+ # Accepts partial attributes like archived, name, description, workspace_access
61
+ def update_pronunciation_dictionary(pronunciation_dictionary_id, **attributes)
62
+ raise ArgumentError, "pronunciation_dictionary_id is required" if pronunciation_dictionary_id.nil? || pronunciation_dictionary_id.to_s.strip.empty?
63
+ endpoint = "/v1/pronunciation-dictionaries/#{pronunciation_dictionary_id}"
64
+ @client.patch(endpoint, attributes)
65
+ end
66
+
67
+ alias_method :update, :update_pronunciation_dictionary
68
+
69
+ # GET /v1/pronunciation-dictionaries/:dictionary_id/:version_id/download
70
+ # Returns raw PLS file contents
71
+ def download_pronunciation_dictionary_version(dictionary_id:, version_id:)
72
+ raise ArgumentError, "dictionary_id is required" if dictionary_id.nil? || dictionary_id.to_s.strip.empty?
73
+ raise ArgumentError, "version_id is required" if version_id.nil? || version_id.to_s.strip.empty?
74
+
75
+ endpoint = "/v1/pronunciation-dictionaries/#{dictionary_id}/#{version_id}/download"
76
+ @client.get_binary(endpoint)
77
+ end
78
+
79
+ alias_method :download_version, :download_pronunciation_dictionary_version
80
+
81
+ # GET /v1/pronunciation-dictionaries
82
+ # Optional query: cursor, page_size, sort, sort_direction
83
+ def list_pronunciation_dictionaries(cursor: nil, page_size: nil, sort: nil, sort_direction: nil)
84
+ params = {
85
+ cursor: cursor,
86
+ page_size: page_size,
87
+ sort: sort,
88
+ sort_direction: sort_direction
89
+ }.compact
90
+
91
+ @client.get("/v1/pronunciation-dictionaries", params)
92
+ end
93
+
94
+ alias_method :list, :list_pronunciation_dictionaries
95
+
96
+ private
97
+
98
+ attr_reader :client
99
+ end
100
+ end
101
+ end
102
+
103
+