kapso-client-ruby 1.0.0 → 1.0.2

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -81
  3. data/CHANGELOG.md +262 -91
  4. data/Gemfile +20 -20
  5. data/RAILS_INTEGRATION.md +478 -0
  6. data/README.md +1053 -734
  7. data/Rakefile +40 -40
  8. data/TEMPLATE_TOOLS_GUIDE.md +120 -120
  9. data/WHATSAPP_24_HOUR_GUIDE.md +133 -133
  10. data/examples/advanced_features.rb +352 -349
  11. data/examples/advanced_messaging.rb +241 -0
  12. data/examples/basic_messaging.rb +139 -136
  13. data/examples/enhanced_interactive.rb +400 -0
  14. data/examples/flows_usage.rb +307 -0
  15. data/examples/interactive_messages.rb +343 -0
  16. data/examples/media_management.rb +256 -253
  17. data/examples/rails/jobs.rb +388 -0
  18. data/examples/rails/models.rb +240 -0
  19. data/examples/rails/notifications_controller.rb +227 -0
  20. data/examples/template_management.rb +393 -390
  21. data/kapso-ruby-logo.jpg +0 -0
  22. data/lib/kapso_client_ruby/client.rb +321 -316
  23. data/lib/kapso_client_ruby/errors.rb +348 -329
  24. data/lib/kapso_client_ruby/rails/generators/install_generator.rb +76 -0
  25. data/lib/kapso_client_ruby/rails/generators/templates/env.erb +21 -0
  26. data/lib/kapso_client_ruby/rails/generators/templates/initializer.rb.erb +33 -0
  27. data/lib/kapso_client_ruby/rails/generators/templates/message_service.rb.erb +138 -0
  28. data/lib/kapso_client_ruby/rails/generators/templates/webhook_controller.rb.erb +62 -0
  29. data/lib/kapso_client_ruby/rails/railtie.rb +55 -0
  30. data/lib/kapso_client_ruby/rails/service.rb +189 -0
  31. data/lib/kapso_client_ruby/rails/tasks.rake +167 -0
  32. data/lib/kapso_client_ruby/resources/calls.rb +172 -172
  33. data/lib/kapso_client_ruby/resources/contacts.rb +190 -190
  34. data/lib/kapso_client_ruby/resources/conversations.rb +103 -103
  35. data/lib/kapso_client_ruby/resources/flows.rb +382 -0
  36. data/lib/kapso_client_ruby/resources/media.rb +205 -205
  37. data/lib/kapso_client_ruby/resources/messages.rb +760 -380
  38. data/lib/kapso_client_ruby/resources/phone_numbers.rb +85 -85
  39. data/lib/kapso_client_ruby/resources/templates.rb +283 -283
  40. data/lib/kapso_client_ruby/types.rb +348 -262
  41. data/lib/kapso_client_ruby/version.rb +5 -5
  42. data/lib/kapso_client_ruby.rb +75 -68
  43. data/scripts/.env.example +17 -17
  44. data/scripts/kapso_template_finder.rb +91 -91
  45. data/scripts/sdk_setup.rb +404 -404
  46. data/scripts/test.rb +60 -60
  47. metadata +24 -3
@@ -1,330 +1,349 @@
1
- # frozen_string_literal: true
2
-
3
- module KapsoClientRuby
4
- module Errors
5
- # Error categories mapped from the JavaScript implementation
6
- ERROR_CATEGORIES = {
7
- 'authorization' => :authorization,
8
- 'permission' => :permission,
9
- 'parameter' => :parameter,
10
- 'throttling' => :throttling,
11
- 'template' => :template,
12
- 'media' => :media,
13
- 'phone_registration' => :phone_registration,
14
- 'integrity' => :integrity,
15
- 'business_eligibility' => :business_eligibility,
16
- 'reengagement_window' => :reengagement_window,
17
- 'waba_config' => :waba_config,
18
- 'flow' => :flow,
19
- 'synchronization' => :synchronization,
20
- 'server' => :server,
21
- 'unknown' => :unknown
22
- }.freeze
23
-
24
- # Error codes and their categories
25
- ERROR_CODE_CATEGORIES = {
26
- 0 => :authorization,
27
- 190 => :authorization,
28
- 3 => :permission,
29
- 10 => :permission,
30
- (200..219) => :permission,
31
- 4 => :throttling,
32
- 80007 => :throttling,
33
- 130429 => :throttling,
34
- 131048 => :throttling,
35
- 131056 => :throttling,
36
- 33 => :parameter,
37
- 100 => :parameter,
38
- 130472 => :parameter,
39
- 131008 => :parameter,
40
- 131009 => :parameter,
41
- 131021 => :parameter,
42
- 131026 => :parameter,
43
- 131051 => :media,
44
- 131052 => :media,
45
- 131053 => :media,
46
- 131000 => :server,
47
- 131016 => :server,
48
- 131057 => :server,
49
- 133004 => :server,
50
- 133005 => :server,
51
- 368 => :integrity,
52
- 130497 => :integrity,
53
- 131031 => :integrity,
54
- 131047 => :reengagement_window,
55
- 131037 => :waba_config,
56
- 131042 => :business_eligibility,
57
- 131045 => :phone_registration,
58
- 133000 => :phone_registration,
59
- 133006 => :phone_registration,
60
- 133008 => :phone_registration,
61
- 133009 => :phone_registration,
62
- 133010 => :phone_registration,
63
- 133015 => :phone_registration,
64
- 133016 => :phone_registration,
65
- 132000 => :template,
66
- 132001 => :template,
67
- 132005 => :template,
68
- 132007 => :template,
69
- 132012 => :template,
70
- 132015 => :template,
71
- 132016 => :template,
72
- 132068 => :flow,
73
- 132069 => :flow,
74
- 134011 => :business_eligibility,
75
- 135000 => :parameter,
76
- 2593107 => :synchronization,
77
- 2593108 => :synchronization
78
- }.freeze
79
-
80
- # Error codes that should not be retried
81
- DO_NOT_RETRY_CODES = [131049, 131050, 131047, 368, 130497, 131031].freeze
82
-
83
- # Error codes that require token refresh
84
- REFRESH_TOKEN_CODES = [0, 190].freeze
85
-
86
- class GraphApiError < StandardError
87
- attr_reader :http_status, :code, :type, :details, :error_subcode,
88
- :fbtrace_id, :error_data, :category, :retry_hint, :raw_response, :retry_after
89
-
90
- def initialize(message: nil, http_status:, code: nil, type: nil, details: nil,
91
- error_subcode: nil, fbtrace_id: nil, error_data: nil,
92
- category: nil, retry_hint: nil, raw_response: nil, retry_after: nil)
93
- @http_status = http_status
94
- @code = code || http_status
95
- @type = type || 'GraphApiError'
96
- @details = details
97
- @error_subcode = error_subcode
98
- @fbtrace_id = fbtrace_id
99
- @error_data = error_data
100
- @retry_after = retry_after
101
- @category = category || categorize_error_code(@code, @http_status)
102
- @retry_hint = retry_hint || derive_retry_hint
103
- @raw_response = raw_response
104
-
105
- error_message = message || build_error_message
106
- super(error_message)
107
- end
108
-
109
- class << self
110
- def from_response(response, body = nil, raw_text = nil)
111
- http_status = response.status
112
- retry_after_ms = parse_retry_after(response.headers['retry-after'])
113
-
114
- # Ensure body is a hash for processing
115
- unless body.is_a?(Hash)
116
- if body.is_a?(String)
117
- begin
118
- body = JSON.parse(body)
119
- rescue JSON::ParserError
120
- body = {}
121
- end
122
- else
123
- body = {}
124
- end
125
- end
126
-
127
- # Check for Graph API error envelope
128
- if body.key?('error')
129
- error_payload = body['error']
130
- code = error_payload['code'] || http_status
131
- type = error_payload['type'] || 'GraphApiError'
132
- details = error_payload.is_a?(Hash) ? error_payload.dig('error_data', 'details') : nil
133
-
134
- new(
135
- message: error_payload['message'],
136
- http_status: http_status,
137
- code: code,
138
- type: type,
139
- details: details,
140
- error_subcode: error_payload['error_subcode'],
141
- fbtrace_id: error_payload['fbtrace_id'],
142
- error_data: error_payload['error_data'],
143
- retry_hint: build_retry_hint_with_delay(code, http_status, retry_after_ms),
144
- raw_response: body
145
- )
146
- elsif body.is_a?(Hash) && body.key?('error') && body['error'].is_a?(String)
147
- # Kapso proxy error format
148
- category = http_status >= 500 ? :server : categorize_error_code(nil, http_status)
149
- new(
150
- message: body['error'],
151
- http_status: http_status,
152
- code: http_status,
153
- category: category,
154
- retry_hint: build_retry_hint_with_delay(http_status, http_status, retry_after_ms),
155
- raw_response: body
156
- )
157
- else
158
- # Generic HTTP error
159
- category = http_status >= 500 ? :server : categorize_error_code(nil, http_status)
160
- message = build_default_message(http_status, nil, raw_text)
161
-
162
- new(
163
- message: message,
164
- http_status: http_status,
165
- code: http_status,
166
- category: category,
167
- retry_hint: build_retry_hint_with_delay(http_status, http_status, retry_after_ms),
168
- raw_response: raw_text || body
169
- )
170
- end
171
- end
172
-
173
- private
174
-
175
- def parse_retry_after(header)
176
- return nil unless header
177
-
178
- # Try parsing as number of seconds
179
- if header.match?(/^\d+$/)
180
- header.to_i * 1000
181
- else
182
- # Try parsing as HTTP date
183
- begin
184
- date = Time.parse(header)
185
- diff = (date.to_f - Time.now.to_f) * 1000
186
- diff > 0 ? diff.to_i : 0
187
- rescue ArgumentError
188
- nil
189
- end
190
- end
191
- end
192
-
193
- def categorize_error_code(code, http_status)
194
- return :authorization if http_status == 401
195
- return :permission if http_status == 403
196
- return :parameter if http_status == 404
197
- return :throttling if http_status == 429
198
- return :server if http_status >= 500
199
- return :parameter if http_status >= 400 && http_status < 500
200
-
201
- if code
202
- ERROR_CODE_CATEGORIES.each do |key, category|
203
- if key.is_a?(Range)
204
- return category if key.include?(code)
205
- elsif key == code
206
- return category
207
- end
208
- end
209
-
210
- # Check permission range
211
- return :permission if code >= 200 && code <= 299
212
- end
213
-
214
- :unknown
215
- end
216
-
217
- def build_retry_hint_with_delay(code, http_status, retry_after_ms)
218
- if retry_after_ms
219
- { action: :retry_after, retry_after_ms: retry_after_ms }
220
- elsif DO_NOT_RETRY_CODES.include?(code)
221
- { action: :do_not_retry }
222
- elsif REFRESH_TOKEN_CODES.include?(code)
223
- { action: :refresh_token }
224
- elsif http_status >= 500
225
- { action: :retry }
226
- else
227
- { action: :fix_and_retry }
228
- end
229
- end
230
-
231
- def build_default_message(status, details = nil, raw_text = nil)
232
- if details
233
- "Meta API request failed with status #{status}: #{details}"
234
- elsif raw_text && !raw_text.strip.empty?
235
- "Meta API request failed with status #{status}: #{raw_text}"
236
- else
237
- "Meta API request failed with status #{status}"
238
- end
239
- end
240
- end
241
-
242
- def auth_error?
243
- category == :authorization
244
- end
245
-
246
- def rate_limit?
247
- category == :throttling
248
- end
249
-
250
- def temporary?
251
- [:throttling, :server, :synchronization].include?(category) ||
252
- http_status >= 500 ||
253
- [1, 2, 17, 341].include?(code)
254
- end
255
-
256
- def template_error?
257
- category == :template
258
- end
259
-
260
- def requires_token_refresh?
261
- category == :authorization || REFRESH_TOKEN_CODES.include?(code)
262
- end
263
-
264
- def retryable?
265
- ![:do_not_retry].include?(retry_hint[:action])
266
- end
267
-
268
- def to_h
269
- {
270
- name: self.class.name,
271
- message: message,
272
- http_status: http_status,
273
- code: code,
274
- type: type,
275
- details: details,
276
- error_subcode: error_subcode,
277
- fbtrace_id: fbtrace_id,
278
- category: category,
279
- retry_hint: retry_hint,
280
- raw_response: raw_response
281
- }
282
- end
283
-
284
- private
285
-
286
- def categorize_error_code(code, http_status)
287
- self.class.send(:categorize_error_code, code, http_status)
288
- end
289
-
290
- def derive_retry_hint
291
- if DO_NOT_RETRY_CODES.include?(code)
292
- { action: :do_not_retry }
293
- elsif REFRESH_TOKEN_CODES.include?(code)
294
- { action: :refresh_token }
295
- elsif http_status >= 500
296
- { action: :retry }
297
- else
298
- { action: :fix_and_retry }
299
- end
300
- end
301
-
302
- def build_error_message
303
- if details
304
- "Meta API request failed with status #{http_status}: #{details}"
305
- elsif raw_response.is_a?(String) && !raw_response.strip.empty?
306
- "Meta API request failed with status #{http_status}: #{raw_response}"
307
- else
308
- "Meta API request failed with status #{http_status}"
309
- end
310
- end
311
- end
312
-
313
- class KapsoProxyRequiredError < StandardError
314
- attr_reader :feature, :help_url
315
-
316
- def initialize(feature)
317
- @feature = feature
318
- @help_url = 'https://kapso.ai/'
319
-
320
- message = "#{feature} is only available via the Kapso Proxy. " \
321
- "Set base_url to https://app.kapso.ai/api/meta and provide kapso_api_key. " \
322
- "Create a free account at #{help_url}"
323
- super(message)
324
- end
325
- end
326
-
327
- class ConfigurationError < StandardError; end
328
- class ValidationError < StandardError; end
329
- end
1
+ # frozen_string_literal: true
2
+
3
+ module KapsoClientRuby
4
+ module Errors
5
+ # Error categories mapped from the JavaScript implementation
6
+ ERROR_CATEGORIES = {
7
+ 'authorization' => :authorization,
8
+ 'permission' => :permission,
9
+ 'parameter' => :parameter,
10
+ 'throttling' => :throttling,
11
+ 'template' => :template,
12
+ 'media' => :media,
13
+ 'phone_registration' => :phone_registration,
14
+ 'integrity' => :integrity,
15
+ 'business_eligibility' => :business_eligibility,
16
+ 'reengagement_window' => :reengagement_window,
17
+ 'waba_config' => :waba_config,
18
+ 'flow' => :flow,
19
+ 'synchronization' => :synchronization,
20
+ 'server' => :server,
21
+ 'unknown' => :unknown
22
+ }.freeze
23
+
24
+ # Error codes and their categories
25
+ ERROR_CODE_CATEGORIES = {
26
+ 0 => :authorization,
27
+ 190 => :authorization,
28
+ 3 => :permission,
29
+ 10 => :permission,
30
+ (200..219) => :permission,
31
+ 4 => :throttling,
32
+ 80007 => :throttling,
33
+ 130429 => :throttling,
34
+ 131048 => :throttling,
35
+ 131056 => :throttling,
36
+ 33 => :parameter,
37
+ 100 => :parameter,
38
+ 130472 => :parameter,
39
+ 131008 => :parameter,
40
+ 131009 => :parameter,
41
+ 131021 => :parameter,
42
+ 131026 => :parameter,
43
+ 131051 => :media,
44
+ 131052 => :media,
45
+ 131053 => :media,
46
+ 131000 => :server,
47
+ 131016 => :server,
48
+ 131057 => :server,
49
+ 133004 => :server,
50
+ 133005 => :server,
51
+ 368 => :integrity,
52
+ 130497 => :integrity,
53
+ 131031 => :integrity,
54
+ 131047 => :reengagement_window,
55
+ 131037 => :waba_config,
56
+ 131042 => :business_eligibility,
57
+ 131045 => :phone_registration,
58
+ 133000 => :phone_registration,
59
+ 133006 => :phone_registration,
60
+ 133008 => :phone_registration,
61
+ 133009 => :phone_registration,
62
+ 133010 => :phone_registration,
63
+ 133015 => :phone_registration,
64
+ 133016 => :phone_registration,
65
+ 132000 => :template,
66
+ 132001 => :template,
67
+ 132005 => :template,
68
+ 132007 => :template,
69
+ 132012 => :template,
70
+ 132015 => :template,
71
+ 132016 => :template,
72
+ 132068 => :flow,
73
+ 132069 => :flow,
74
+ 134011 => :business_eligibility,
75
+ 135000 => :parameter,
76
+ 2593107 => :synchronization,
77
+ 2593108 => :synchronization
78
+ }.freeze
79
+
80
+ # Error codes that should not be retried
81
+ DO_NOT_RETRY_CODES = [131049, 131050, 131047, 368, 130497, 131031].freeze
82
+
83
+ # Error codes that require token refresh
84
+ REFRESH_TOKEN_CODES = [0, 190].freeze
85
+
86
+ class GraphApiError < StandardError
87
+ attr_reader :http_status, :code, :type, :details, :error_subcode,
88
+ :fbtrace_id, :error_data, :category, :retry_hint, :raw_response, :retry_after
89
+
90
+ def initialize(message: nil, http_status:, code: nil, type: nil, details: nil,
91
+ error_subcode: nil, fbtrace_id: nil, error_data: nil,
92
+ category: nil, retry_hint: nil, raw_response: nil, retry_after: nil)
93
+ @http_status = http_status
94
+ @code = code || http_status
95
+ @type = type || 'GraphApiError'
96
+ @details = details
97
+ @error_subcode = error_subcode
98
+ @fbtrace_id = fbtrace_id
99
+ @error_data = error_data
100
+ @retry_after = retry_after
101
+ @category = category || categorize_error_code(@code, @http_status)
102
+ @retry_hint = retry_hint || derive_retry_hint
103
+ @raw_response = raw_response
104
+
105
+ error_message = message || build_error_message
106
+ super(error_message)
107
+ end
108
+
109
+ class << self
110
+ def from_response(response, body = nil, raw_text = nil)
111
+ http_status = response.status
112
+ retry_after_ms = parse_retry_after(response.headers['retry-after'])
113
+
114
+ # Ensure body is a hash for processing
115
+ unless body.is_a?(Hash)
116
+ if body.is_a?(String)
117
+ begin
118
+ body = JSON.parse(body)
119
+ rescue JSON::ParserError
120
+ body = {}
121
+ end
122
+ else
123
+ body = {}
124
+ end
125
+ end
126
+
127
+ # Check for Graph API error envelope
128
+ if body.key?('error')
129
+ error_payload = body['error']
130
+ code = error_payload['code'] || http_status
131
+ type = error_payload['type'] || 'GraphApiError'
132
+ details = error_payload.is_a?(Hash) ? error_payload.dig('error_data', 'details') : nil
133
+
134
+ new(
135
+ message: error_payload['message'],
136
+ http_status: http_status,
137
+ code: code,
138
+ type: type,
139
+ details: details,
140
+ error_subcode: error_payload['error_subcode'],
141
+ fbtrace_id: error_payload['fbtrace_id'],
142
+ error_data: error_payload['error_data'],
143
+ retry_hint: build_retry_hint_with_delay(code, http_status, retry_after_ms),
144
+ raw_response: body
145
+ )
146
+ elsif body.is_a?(Hash) && body.key?('error') && body['error'].is_a?(String)
147
+ # Kapso proxy error format
148
+ category = http_status >= 500 ? :server : categorize_error_code(nil, http_status)
149
+ new(
150
+ message: body['error'],
151
+ http_status: http_status,
152
+ code: http_status,
153
+ category: category,
154
+ retry_hint: build_retry_hint_with_delay(http_status, http_status, retry_after_ms),
155
+ raw_response: body
156
+ )
157
+ else
158
+ # Generic HTTP error
159
+ category = http_status >= 500 ? :server : categorize_error_code(nil, http_status)
160
+ message = build_default_message(http_status, nil, raw_text)
161
+
162
+ new(
163
+ message: message,
164
+ http_status: http_status,
165
+ code: http_status,
166
+ category: category,
167
+ retry_hint: build_retry_hint_with_delay(http_status, http_status, retry_after_ms),
168
+ raw_response: raw_text || body
169
+ )
170
+ end
171
+ end
172
+
173
+ private
174
+
175
+ def parse_retry_after(header)
176
+ return nil unless header
177
+
178
+ # Try parsing as number of seconds
179
+ if header.match?(/^\d+$/)
180
+ header.to_i * 1000
181
+ else
182
+ # Try parsing as HTTP date
183
+ begin
184
+ date = Time.parse(header)
185
+ diff = (date.to_f - Time.now.to_f) * 1000
186
+ diff > 0 ? diff.to_i : 0
187
+ rescue ArgumentError
188
+ nil
189
+ end
190
+ end
191
+ end
192
+
193
+ def categorize_error_code(code, http_status)
194
+ return :authorization if http_status == 401
195
+ return :permission if http_status == 403
196
+ return :parameter if http_status == 404
197
+ return :throttling if http_status == 429
198
+ return :server if http_status >= 500
199
+ return :parameter if http_status >= 400 && http_status < 500
200
+
201
+ if code
202
+ ERROR_CODE_CATEGORIES.each do |key, category|
203
+ if key.is_a?(Range)
204
+ return category if key.include?(code)
205
+ elsif key == code
206
+ return category
207
+ end
208
+ end
209
+
210
+ # Check permission range
211
+ return :permission if code >= 200 && code <= 299
212
+ end
213
+
214
+ :unknown
215
+ end
216
+
217
+ def build_retry_hint_with_delay(code, http_status, retry_after_ms)
218
+ if retry_after_ms
219
+ { action: :retry_after, retry_after_ms: retry_after_ms }
220
+ elsif DO_NOT_RETRY_CODES.include?(code)
221
+ { action: :do_not_retry }
222
+ elsif REFRESH_TOKEN_CODES.include?(code)
223
+ { action: :refresh_token }
224
+ elsif http_status >= 500
225
+ { action: :retry }
226
+ else
227
+ { action: :fix_and_retry }
228
+ end
229
+ end
230
+
231
+ def build_default_message(status, details = nil, raw_text = nil)
232
+ if details
233
+ "Meta API request failed with status #{status}: #{details}"
234
+ elsif raw_text && !raw_text.strip.empty?
235
+ "Meta API request failed with status #{status}: #{raw_text}"
236
+ else
237
+ "Meta API request failed with status #{status}"
238
+ end
239
+ end
240
+ end
241
+
242
+ def auth_error?
243
+ category == :authorization
244
+ end
245
+
246
+ def rate_limit?
247
+ category == :throttling
248
+ end
249
+
250
+ def temporary?
251
+ [:throttling, :server, :synchronization].include?(category) ||
252
+ http_status >= 500 ||
253
+ [1, 2, 17, 341].include?(code)
254
+ end
255
+
256
+ def template_error?
257
+ category == :template
258
+ end
259
+
260
+ def requires_token_refresh?
261
+ category == :authorization || REFRESH_TOKEN_CODES.include?(code)
262
+ end
263
+
264
+ def retryable?
265
+ ![:do_not_retry].include?(retry_hint[:action])
266
+ end
267
+
268
+ def to_h
269
+ {
270
+ name: self.class.name,
271
+ message: message,
272
+ http_status: http_status,
273
+ code: code,
274
+ type: type,
275
+ details: details,
276
+ error_subcode: error_subcode,
277
+ fbtrace_id: fbtrace_id,
278
+ category: category,
279
+ retry_hint: retry_hint,
280
+ raw_response: raw_response
281
+ }
282
+ end
283
+
284
+ private
285
+
286
+ def categorize_error_code(code, http_status)
287
+ self.class.send(:categorize_error_code, code, http_status)
288
+ end
289
+
290
+ def derive_retry_hint
291
+ if DO_NOT_RETRY_CODES.include?(code)
292
+ { action: :do_not_retry }
293
+ elsif REFRESH_TOKEN_CODES.include?(code)
294
+ { action: :refresh_token }
295
+ elsif http_status >= 500
296
+ { action: :retry }
297
+ else
298
+ { action: :fix_and_retry }
299
+ end
300
+ end
301
+
302
+ def build_error_message
303
+ if details
304
+ "Meta API request failed with status #{http_status}: #{details}"
305
+ elsif raw_response.is_a?(String) && !raw_response.strip.empty?
306
+ "Meta API request failed with status #{http_status}: #{raw_response}"
307
+ else
308
+ "Meta API request failed with status #{http_status}"
309
+ end
310
+ end
311
+ end
312
+
313
+ class KapsoProxyRequiredError < StandardError
314
+ attr_reader :feature, :help_url
315
+
316
+ def initialize(feature)
317
+ @feature = feature
318
+ @help_url = 'https://kapso.ai/'
319
+
320
+ message = "#{feature} is only available via the Kapso Proxy. " \
321
+ "Set base_url to https://app.kapso.ai/api/meta and provide kapso_api_key. " \
322
+ "Create a free account at #{help_url}"
323
+ super(message)
324
+ end
325
+ end
326
+
327
+ class ConfigurationError < StandardError; end
328
+ class ValidationError < StandardError; end
329
+
330
+ # Flow-specific errors
331
+ class FlowDecryptionError < StandardError
332
+ attr_reader :original_error
333
+
334
+ def initialize(message, original_error = nil)
335
+ @original_error = original_error
336
+ super(message)
337
+ end
338
+ end
339
+
340
+ class FlowEncryptionError < StandardError
341
+ attr_reader :original_error
342
+
343
+ def initialize(message, original_error = nil)
344
+ @original_error = original_error
345
+ super(message)
346
+ end
347
+ end
348
+ end
330
349
  end