ai-lite 0.6.0 → 1.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.
data/test/ai_lite_test.rb CHANGED
@@ -20,8 +20,27 @@ class AiLiteTest < Minitest::Test
20
20
  end
21
21
  end
22
22
 
23
+ def provider
24
+ AiLite::OpenAI
25
+ end
26
+
23
27
  def setup
24
- AiLite.reset_configuration!
28
+ provider.reset_configuration!
29
+ end
30
+
31
+ def test_http_200_failed_or_incomplete_generation_is_not_success
32
+ client = provider.new(api_key: "test-key")
33
+ %w[failed cancelled incomplete].each do |state|
34
+ body = JSON.generate("id" => "resp_partial", "status" => state,
35
+ "incomplete_details" => { "reason" => "max_output_tokens" }, "output" => [])
36
+ with_stubbed_http(FakeResponse.new("200", body)) do
37
+ result = client.chat("Hi", debug: true)
38
+ assert_equal 200, result["status"]
39
+ assert_includes result["error"], state
40
+ assert_equal "resp_partial", result["response_id"]
41
+ refute_nil result["raw"]
42
+ end
43
+ end
25
44
  end
26
45
 
27
46
  def test_has_a_version_number
@@ -30,7 +49,7 @@ class AiLiteTest < Minitest::Test
30
49
 
31
50
  def test_stores_initializer_config_and_headers
32
51
  with_env("OPENAI_API_KEY" => nil, "OPEN_AI_TOKEN" => nil) do
33
- client = AiLite.new(
52
+ client = provider.new(
34
53
  api_key: "explicit-key",
35
54
  model: "gpt-test",
36
55
  image_model: "gpt-image-test",
@@ -57,7 +76,7 @@ class AiLiteTest < Minitest::Test
57
76
 
58
77
  def test_configure_sets_defaults_for_client
59
78
  with_env("OPENAI_API_KEY" => nil, "OPEN_AI_TOKEN" => nil) do
60
- AiLite.configure do |config|
79
+ provider.configure do |config|
61
80
  config.api_key = "configured-key"
62
81
  config.model = "gpt-config"
63
82
  config.moderation_model = "omni-moderation-test"
@@ -70,7 +89,7 @@ class AiLiteTest < Minitest::Test
70
89
  config.max_output_tokens = 750
71
90
  end
72
91
 
73
- client = AiLite.client
92
+ client = provider.client
74
93
 
75
94
  assert_equal "configured-key", client.api_key
76
95
  assert_equal "gpt-config", client.model
@@ -82,23 +101,23 @@ class AiLiteTest < Minitest::Test
82
101
  assert_equal "gpt-transcribe-test", client.transcription_model
83
102
  assert_equal 15, client.timeout
84
103
  assert_equal 750, client.max_output_tokens
85
- assert_same client, AiLite.client
104
+ assert_same client, provider.client
86
105
  end
87
106
  end
88
107
 
89
108
  def test_configure_resets_cached_client
90
- AiLite.configure { |config| config.api_key = "first-key" }
91
- first_client = AiLite.client
109
+ provider.configure { |config| config.api_key = "first-key" }
110
+ first_client = provider.client
92
111
 
93
- AiLite.configure { |config| config.api_key = "second-key" }
94
- second_client = AiLite.client
112
+ provider.configure { |config| config.api_key = "second-key" }
113
+ second_client = provider.client
95
114
 
96
115
  refute_same first_client, second_client
97
116
  assert_equal "second-key", second_client.api_key
98
117
  end
99
118
 
100
119
  def test_instance_arguments_override_configuration
101
- AiLite.configure do |config|
120
+ provider.configure do |config|
102
121
  config.api_key = "configured-key"
103
122
  config.model = "gpt-config"
104
123
  config.moderation_model = "omni-moderation-config"
@@ -111,7 +130,7 @@ class AiLiteTest < Minitest::Test
111
130
  config.max_output_tokens = 750
112
131
  end
113
132
 
114
- client = AiLite.new(
133
+ client = provider.new(
115
134
  api_key: "explicit-key",
116
135
  model: "gpt-explicit",
117
136
  moderation_model: "omni-moderation-explicit",
@@ -138,32 +157,32 @@ class AiLiteTest < Minitest::Test
138
157
 
139
158
  def test_api_key_fallback_order
140
159
  with_env("OPENAI_API_KEY" => "openai-key", "OPEN_AI_TOKEN" => "legacy-key") do
141
- assert_equal "openai-key", AiLite.new.api_key
142
- assert_equal "explicit-key", AiLite.new(api_key: "explicit-key").api_key
160
+ assert_equal "openai-key", provider.new.api_key
161
+ assert_equal "explicit-key", provider.new(api_key: "explicit-key").api_key
143
162
  end
144
163
 
145
164
  with_env("OPENAI_API_KEY" => nil, "OPEN_AI_TOKEN" => "legacy-key") do
146
- assert_equal "legacy-key", AiLite.new.api_key
165
+ assert_equal "legacy-key", provider.new.api_key
147
166
  end
148
167
  end
149
168
 
150
169
  def test_configured_api_key_takes_precedence_over_env
151
- AiLite.configure { |config| config.api_key = "configured-key" }
170
+ provider.configure { |config| config.api_key = "configured-key" }
152
171
 
153
172
  with_env("OPENAI_API_KEY" => "openai-key", "OPEN_AI_TOKEN" => "legacy-key") do
154
- assert_equal "configured-key", AiLite.new.api_key
155
- assert_equal "explicit-key", AiLite.new(api_key: "explicit-key").api_key
173
+ assert_equal "configured-key", provider.new.api_key
174
+ assert_equal "explicit-key", provider.new(api_key: "explicit-key").api_key
156
175
  end
157
176
  end
158
177
 
159
178
  def test_missing_api_key_raises
160
179
  with_env("OPENAI_API_KEY" => nil, "OPEN_AI_TOKEN" => nil) do
161
- assert_raises(ArgumentError) { AiLite.new }
180
+ assert_raises(ArgumentError) { provider.new }
162
181
  end
163
182
  end
164
183
 
165
184
  def test_chat_sends_post_to_responses_with_default_payload
166
- client = AiLite.new(api_key: "token-abc")
185
+ client = provider.new(api_key: "token-abc")
167
186
 
168
187
  with_stubbed_http(success_response("Hello!")) do |captured, _response|
169
188
  result = client.chat("Say hello")
@@ -189,7 +208,7 @@ class AiLiteTest < Minitest::Test
189
208
  end
190
209
 
191
210
  def test_chat_allows_overriding_max_output_tokens
192
- client = AiLite.new(api_key: "token-abc")
211
+ client = provider.new(api_key: "token-abc")
193
212
 
194
213
  with_stubbed_http(success_response("Short")) do |captured, _response|
195
214
  client.chat("Summarize", max_output_tokens: 500)
@@ -200,13 +219,13 @@ class AiLiteTest < Minitest::Test
200
219
  end
201
220
 
202
221
  def test_chat_uses_configured_max_output_tokens
203
- AiLite.configure do |config|
222
+ provider.configure do |config|
204
223
  config.api_key = "configured-key"
205
224
  config.max_output_tokens = 750
206
225
  end
207
226
 
208
227
  with_stubbed_http(success_response("Configured")) do |captured, _response|
209
- AiLite.chat("Use configured defaults")
228
+ provider.chat("Use configured defaults")
210
229
  payload = JSON.parse(captured[:http].last_request.body)
211
230
 
212
231
  assert_equal 750, payload["max_output_tokens"]
@@ -215,7 +234,7 @@ class AiLiteTest < Minitest::Test
215
234
  end
216
235
 
217
236
  def test_chat_includes_instructions_model_and_options
218
- client = AiLite.new(api_key: "token-abc")
237
+ client = provider.new(api_key: "token-abc")
219
238
 
220
239
  with_stubbed_http(success_response("Aye")) do |captured, _response|
221
240
  client.chat(
@@ -238,15 +257,13 @@ class AiLiteTest < Minitest::Test
238
257
  end
239
258
  end
240
259
 
241
- def test_chat_supports_previous_response_id_through_options
242
- client = AiLite.new(api_key: "token-abc")
260
+ def test_chat_supports_previous_response_id
261
+ client = provider.new(api_key: "token-abc")
243
262
 
244
263
  with_stubbed_http(success_response("Follow-up")) do |captured, _response|
245
264
  result = client.chat(
246
265
  "Explain why that is funny",
247
- options: {
248
- previous_response_id: "resp_previous_123"
249
- }
266
+ previous_response_id: "resp_previous_123"
250
267
  )
251
268
  payload = JSON.parse(captured[:http].last_request.body)
252
269
 
@@ -256,8 +273,55 @@ class AiLiteTest < Minitest::Test
256
273
  end
257
274
  end
258
275
 
276
+ def test_chat_supports_message_array_input
277
+ client = provider.new(api_key: "token-abc")
278
+ messages = [
279
+ { role: "developer", content: "Be concise." },
280
+ { role: "user", content: "Say hello." }
281
+ ]
282
+
283
+ with_stubbed_http(success_response("Hello!")) do |captured, _response|
284
+ client.chat(messages)
285
+ payload = JSON.parse(captured[:http].last_request.body)
286
+
287
+ assert_equal(
288
+ [
289
+ { "role" => "developer", "content" => "Be concise." },
290
+ { "role" => "user", "content" => "Say hello." }
291
+ ],
292
+ payload["input"]
293
+ )
294
+ end
295
+ end
296
+
297
+ def test_chat_first_class_previous_response_id_overrides_options
298
+ client = provider.new(api_key: "token-abc")
299
+
300
+ with_stubbed_http(success_response("Follow-up")) do |captured, _response|
301
+ client.chat(
302
+ "Continue",
303
+ previous_response_id: "resp_keyword",
304
+ options: { previous_response_id: "resp_options" }
305
+ )
306
+ payload = JSON.parse(captured[:http].last_request.body)
307
+
308
+ assert_equal "resp_keyword", payload["previous_response_id"]
309
+ end
310
+ end
311
+
312
+ def test_chat_keeps_supporting_previous_response_id_through_options
313
+ client = provider.new(api_key: "token-abc")
314
+
315
+ with_stubbed_http(success_response("Follow-up")) do |captured, _response|
316
+ client.chat("Continue", options: { previous_response_id: "resp_options" })
317
+ payload = JSON.parse(captured[:http].last_request.body)
318
+
319
+ assert_equal "resp_options", payload["previous_response_id"]
320
+ end
321
+ end
322
+
259
323
  def test_extracts_text_from_nested_output_text_items
260
- client = AiLite.new(api_key: "token-abc")
324
+ client = provider.new(api_key: "token-abc")
261
325
  body = JSON.generate(
262
326
  "id" => "resp_nested_123",
263
327
  "output" => [
@@ -283,7 +347,7 @@ class AiLiteTest < Minitest::Test
283
347
  end
284
348
 
285
349
  def test_parses_json_looking_output_with_string_keys
286
- client = AiLite.new(api_key: "token-abc")
350
+ client = provider.new(api_key: "token-abc")
287
351
 
288
352
  with_stubbed_http(success_response(JSON.generate("valid" => true))) do |_captured, _response|
289
353
  result = client.chat("Validate")
@@ -296,7 +360,7 @@ class AiLiteTest < Minitest::Test
296
360
  end
297
361
 
298
362
  def test_debug_true_returns_raw_success_response
299
- client = AiLite.new(api_key: "token-abc")
363
+ client = provider.new(api_key: "token-abc")
300
364
 
301
365
  with_stubbed_http(success_response("Hello!")) do |_captured, _response|
302
366
  result = client.chat("Say hello", debug: true)
@@ -319,7 +383,7 @@ class AiLiteTest < Minitest::Test
319
383
  end
320
384
 
321
385
  def test_moderate_sends_post_to_moderations_with_default_payload
322
- client = AiLite.new(api_key: "token-abc")
386
+ client = provider.new(api_key: "token-abc")
323
387
 
324
388
  with_stubbed_http(moderation_response) do |captured, _response|
325
389
  result = client.moderate("Some user submitted text")
@@ -344,13 +408,13 @@ class AiLiteTest < Minitest::Test
344
408
  end
345
409
 
346
410
  def test_moderate_uses_class_level_configured_client
347
- AiLite.configure do |config|
411
+ provider.configure do |config|
348
412
  config.api_key = "configured-key"
349
413
  config.moderation_model = "omni-moderation-config"
350
414
  end
351
415
 
352
416
  with_stubbed_http(moderation_response) do |captured, _response|
353
- AiLite.moderate("Use configured defaults")
417
+ provider.moderate("Use configured defaults")
354
418
  payload = JSON.parse(captured[:http].last_request.body)
355
419
 
356
420
  assert_equal "omni-moderation-config", payload["model"]
@@ -359,7 +423,7 @@ class AiLiteTest < Minitest::Test
359
423
  end
360
424
 
361
425
  def test_moderate_accepts_text_and_image_url_keywords
362
- client = AiLite.new(api_key: "token-abc")
426
+ client = provider.new(api_key: "token-abc")
363
427
 
364
428
  with_stubbed_http(moderation_response) do |captured, _response|
365
429
  client.moderate(
@@ -382,7 +446,7 @@ class AiLiteTest < Minitest::Test
382
446
  end
383
447
 
384
448
  def test_moderate_accepts_raw_openai_input_shape
385
- client = AiLite.new(api_key: "token-abc")
449
+ client = provider.new(api_key: "token-abc")
386
450
  input = [
387
451
  { type: "text", text: "Caption" },
388
452
  {
@@ -411,7 +475,7 @@ class AiLiteTest < Minitest::Test
411
475
  end
412
476
 
413
477
  def test_moderate_reads_image_path_as_data_url
414
- client = AiLite.new(api_key: "token-abc")
478
+ client = provider.new(api_key: "token-abc")
415
479
 
416
480
  Tempfile.create(["moderation", ".png"]) do |file|
417
481
  file.binmode
@@ -436,7 +500,7 @@ class AiLiteTest < Minitest::Test
436
500
  end
437
501
 
438
502
  def test_moderate_returns_all_results_for_multiple_inputs
439
- client = AiLite.new(api_key: "token-abc")
503
+ client = provider.new(api_key: "token-abc")
440
504
  safe_result = moderation_result(flagged: false)
441
505
  flagged_result = moderation_result(flagged: true)
442
506
 
@@ -450,7 +514,7 @@ class AiLiteTest < Minitest::Test
450
514
  end
451
515
 
452
516
  def test_moderate_local_input_errors_return_standard_envelope
453
- client = AiLite.new(api_key: "token-abc")
517
+ client = provider.new(api_key: "token-abc")
454
518
 
455
519
  result = client.moderate
456
520
 
@@ -462,7 +526,7 @@ class AiLiteTest < Minitest::Test
462
526
  end
463
527
 
464
528
  def test_embed_sends_post_to_embeddings_with_default_payload
465
- client = AiLite.new(api_key: "token-abc")
529
+ client = provider.new(api_key: "token-abc")
466
530
 
467
531
  with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |captured, _response|
468
532
  result = client.embed("Text to vectorize")
@@ -487,7 +551,7 @@ class AiLiteTest < Minitest::Test
487
551
  end
488
552
 
489
553
  def test_embed_returns_vector_list_for_multiple_inputs
490
- client = AiLite.new(api_key: "token-abc")
554
+ client = provider.new(api_key: "token-abc")
491
555
  embeddings = [
492
556
  [0.11, 0.22, 0.33],
493
557
  [0.44, 0.55, 0.66]
@@ -505,7 +569,7 @@ class AiLiteTest < Minitest::Test
505
569
  end
506
570
 
507
571
  def test_embed_includes_options_dimensions_encoding_format_and_model
508
- client = AiLite.new(api_key: "token-abc")
572
+ client = provider.new(api_key: "token-abc")
509
573
 
510
574
  with_stubbed_http(embedding_response([[0.12, -0.34]])) do |captured, _response|
511
575
  client.embed(
@@ -528,13 +592,13 @@ class AiLiteTest < Minitest::Test
528
592
  end
529
593
 
530
594
  def test_embed_uses_class_level_configured_client
531
- AiLite.configure do |config|
595
+ provider.configure do |config|
532
596
  config.api_key = "configured-key"
533
597
  config.embedding_model = "text-embedding-config"
534
598
  end
535
599
 
536
600
  with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |captured, _response|
537
- AiLite.embed("Use configured defaults")
601
+ provider.embed("Use configured defaults")
538
602
  payload = JSON.parse(captured[:http].last_request.body)
539
603
 
540
604
  assert_equal "text-embedding-config", payload["model"]
@@ -543,7 +607,7 @@ class AiLiteTest < Minitest::Test
543
607
  end
544
608
 
545
609
  def test_embed_debug_true_returns_raw_usage
546
- client = AiLite.new(api_key: "token-abc")
610
+ client = provider.new(api_key: "token-abc")
547
611
 
548
612
  with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |_captured, _response|
549
613
  result = client.embed("Text to vectorize", debug: true)
@@ -554,7 +618,7 @@ class AiLiteTest < Minitest::Test
554
618
  end
555
619
 
556
620
  def test_image_sends_post_to_images_with_default_payload
557
- client = AiLite.new(api_key: "token-abc")
621
+ client = provider.new(api_key: "token-abc")
558
622
  image_data = Base64.strict_encode64("fake image")
559
623
 
560
624
  with_stubbed_http(image_response(b64_json: image_data)) do |captured, _response|
@@ -580,7 +644,7 @@ class AiLiteTest < Minitest::Test
580
644
  end
581
645
 
582
646
  def test_image_includes_options_size_quality_background_output_format_and_model
583
- client = AiLite.new(api_key: "token-abc")
647
+ client = provider.new(api_key: "token-abc")
584
648
 
585
649
  with_stubbed_http(image_response) do |captured, _response|
586
650
  client.image(
@@ -609,13 +673,13 @@ class AiLiteTest < Minitest::Test
609
673
  end
610
674
 
611
675
  def test_image_uses_class_level_configured_client
612
- AiLite.configure do |config|
676
+ provider.configure do |config|
613
677
  config.api_key = "configured-key"
614
678
  config.image_model = "gpt-image-config"
615
679
  end
616
680
 
617
681
  with_stubbed_http(image_response) do |captured, _response|
618
- AiLite.image("Use configured defaults")
682
+ provider.image("Use configured defaults")
619
683
  payload = JSON.parse(captured[:http].last_request.body)
620
684
 
621
685
  assert_equal "gpt-image-config", payload["model"]
@@ -624,7 +688,7 @@ class AiLiteTest < Minitest::Test
624
688
  end
625
689
 
626
690
  def test_image_writes_decoded_content_to_output_path
627
- client = AiLite.new(api_key: "token-abc")
691
+ client = provider.new(api_key: "token-abc")
628
692
  image_data = Base64.strict_encode64("fake image")
629
693
 
630
694
  Tempfile.create(["generated", ".png"]) do |file|
@@ -638,7 +702,7 @@ class AiLiteTest < Minitest::Test
638
702
  end
639
703
 
640
704
  def test_image_debug_true_returns_raw_usage
641
- client = AiLite.new(api_key: "token-abc")
705
+ client = provider.new(api_key: "token-abc")
642
706
  image_data = Base64.strict_encode64("fake image")
643
707
 
644
708
  with_stubbed_http(image_response(b64_json: image_data)) do |_captured, _response|
@@ -650,7 +714,7 @@ class AiLiteTest < Minitest::Test
650
714
  end
651
715
 
652
716
  def test_image_output_path_without_image_data_returns_standard_envelope
653
- client = AiLite.new(api_key: "token-abc")
717
+ client = provider.new(api_key: "token-abc")
654
718
 
655
719
  Tempfile.create(["generated", ".png"]) do |file|
656
720
  with_stubbed_http(image_response(b64_json: nil)) do |_captured, _response|
@@ -666,7 +730,7 @@ class AiLiteTest < Minitest::Test
666
730
  end
667
731
 
668
732
  def test_speak_sends_post_to_audio_speech_with_default_payload
669
- client = AiLite.new(api_key: "token-abc")
733
+ client = provider.new(api_key: "token-abc")
670
734
 
671
735
  with_stubbed_http(speech_response("fake audio")) do |captured, _response|
672
736
  result = client.speak("Read this aloud")
@@ -692,7 +756,7 @@ class AiLiteTest < Minitest::Test
692
756
  end
693
757
 
694
758
  def test_speak_includes_options_response_format_speed_instructions_model_and_voice
695
- client = AiLite.new(api_key: "token-abc")
759
+ client = provider.new(api_key: "token-abc")
696
760
 
697
761
  with_stubbed_http(speech_response) do |captured, _response|
698
762
  client.speak(
@@ -719,14 +783,14 @@ class AiLiteTest < Minitest::Test
719
783
  end
720
784
 
721
785
  def test_speak_uses_class_level_configured_client
722
- AiLite.configure do |config|
786
+ provider.configure do |config|
723
787
  config.api_key = "configured-key"
724
788
  config.speech_model = "gpt-speech-config"
725
789
  config.speech_voice = "marin"
726
790
  end
727
791
 
728
792
  with_stubbed_http(speech_response) do |captured, _response|
729
- AiLite.speak("Use configured defaults")
793
+ provider.speak("Use configured defaults")
730
794
  payload = JSON.parse(captured[:http].last_request.body)
731
795
 
732
796
  assert_equal "gpt-speech-config", payload["model"]
@@ -736,7 +800,7 @@ class AiLiteTest < Minitest::Test
736
800
  end
737
801
 
738
802
  def test_speak_writes_audio_to_output_path_and_returns_metadata
739
- client = AiLite.new(api_key: "token-abc")
803
+ client = provider.new(api_key: "token-abc")
740
804
 
741
805
  Tempfile.create(["speech", ".mp3"]) do |file|
742
806
  with_stubbed_http(speech_response("fake audio")) do |_captured, _response|
@@ -756,7 +820,7 @@ class AiLiteTest < Minitest::Test
756
820
  end
757
821
 
758
822
  def test_speak_base64_true_returns_encoded_audio
759
- client = AiLite.new(api_key: "token-abc")
823
+ client = provider.new(api_key: "token-abc")
760
824
 
761
825
  with_stubbed_http(speech_response("fake audio")) do |_captured, _response|
762
826
  result = client.speak("Encode this", base64: true)
@@ -767,7 +831,7 @@ class AiLiteTest < Minitest::Test
767
831
  end
768
832
 
769
833
  def test_speak_debug_true_returns_raw_audio
770
- client = AiLite.new(api_key: "token-abc")
834
+ client = provider.new(api_key: "token-abc")
771
835
 
772
836
  with_stubbed_http(speech_response("fake audio")) do |_captured, _response|
773
837
  result = client.speak("Debug this", debug: true)
@@ -778,7 +842,7 @@ class AiLiteTest < Minitest::Test
778
842
  end
779
843
 
780
844
  def test_speak_http_errors_return_standard_envelope
781
- client = AiLite.new(api_key: "token-abc")
845
+ client = provider.new(api_key: "token-abc")
782
846
  body = JSON.generate("error" => { "message" => "Unsupported voice" })
783
847
 
784
848
  with_stubbed_http(FakeResponse.new("400", body)) do |_captured, _response|
@@ -793,7 +857,7 @@ class AiLiteTest < Minitest::Test
793
857
  end
794
858
 
795
859
  def test_transcribe_sends_post_to_audio_transcriptions_with_default_multipart_payload
796
- client = AiLite.new(api_key: "token-abc")
860
+ client = provider.new(api_key: "token-abc")
797
861
 
798
862
  Tempfile.create(["meeting", ".mp3"]) do |file|
799
863
  file.binmode
@@ -823,7 +887,7 @@ class AiLiteTest < Minitest::Test
823
887
  end
824
888
 
825
889
  def test_transcribe_includes_options_and_optional_fields
826
- client = AiLite.new(api_key: "token-abc")
890
+ client = provider.new(api_key: "token-abc")
827
891
 
828
892
  Tempfile.create(["meeting", ".wav"]) do |file|
829
893
  file.binmode
@@ -863,7 +927,7 @@ class AiLiteTest < Minitest::Test
863
927
  end
864
928
 
865
929
  def test_transcribe_uses_class_level_configured_client
866
- AiLite.configure do |config|
930
+ provider.configure do |config|
867
931
  config.api_key = "configured-key"
868
932
  config.transcription_model = "gpt-transcription-config"
869
933
  end
@@ -874,7 +938,7 @@ class AiLiteTest < Minitest::Test
874
938
  file.flush
875
939
 
876
940
  with_stubbed_http(transcription_response) do |captured, _response|
877
- AiLite.transcribe(file.path)
941
+ provider.transcribe(file.path)
878
942
  request = captured[:http].last_request
879
943
 
880
944
  assert_multipart_field request.body, "model", "gpt-transcription-config"
@@ -884,7 +948,7 @@ class AiLiteTest < Minitest::Test
884
948
  end
885
949
 
886
950
  def test_transcribe_returns_plain_text_response_formats
887
- client = AiLite.new(api_key: "token-abc")
951
+ client = provider.new(api_key: "token-abc")
888
952
 
889
953
  Tempfile.create(["meeting", ".webm"]) do |file|
890
954
  file.binmode
@@ -903,7 +967,7 @@ class AiLiteTest < Minitest::Test
903
967
  end
904
968
 
905
969
  def test_transcribe_debug_true_returns_raw_response
906
- client = AiLite.new(api_key: "token-abc")
970
+ client = provider.new(api_key: "token-abc")
907
971
 
908
972
  Tempfile.create(["meeting", ".ogg"]) do |file|
909
973
  file.binmode
@@ -920,7 +984,7 @@ class AiLiteTest < Minitest::Test
920
984
  end
921
985
 
922
986
  def test_transcribe_http_errors_return_standard_envelope
923
- client = AiLite.new(api_key: "token-abc")
987
+ client = provider.new(api_key: "token-abc")
924
988
  body = JSON.generate("error" => { "message" => "Unsupported audio format" })
925
989
 
926
990
  Tempfile.create(["meeting", ".mp3"]) do |file|
@@ -941,7 +1005,7 @@ class AiLiteTest < Minitest::Test
941
1005
  end
942
1006
 
943
1007
  def test_transcribe_local_file_errors_return_standard_envelope
944
- client = AiLite.new(api_key: "token-abc")
1008
+ client = provider.new(api_key: "token-abc")
945
1009
 
946
1010
  missing_result = client.transcribe("tmp/missing-audio.mp3")
947
1011
  assert_nil missing_result["content"]
@@ -958,7 +1022,7 @@ class AiLiteTest < Minitest::Test
958
1022
  end
959
1023
 
960
1024
  def test_http_errors_return_standard_envelope
961
- client = AiLite.new(api_key: "token-abc")
1025
+ client = provider.new(api_key: "token-abc")
962
1026
  body = JSON.generate("error" => { "message" => "Invalid API key" })
963
1027
 
964
1028
  with_stubbed_http(FakeResponse.new("401", body)) do |_captured, _response|
@@ -973,7 +1037,7 @@ class AiLiteTest < Minitest::Test
973
1037
  end
974
1038
 
975
1039
  def test_debug_true_returns_raw_http_error_response
976
- client = AiLite.new(api_key: "token-abc")
1040
+ client = provider.new(api_key: "token-abc")
977
1041
  body = JSON.generate("error" => { "message" => "Invalid API key" })
978
1042
 
979
1043
  with_stubbed_http(FakeResponse.new("401", body)) do |_captured, _response|
@@ -987,7 +1051,7 @@ class AiLiteTest < Minitest::Test
987
1051
  end
988
1052
 
989
1053
  def test_response_parse_errors_return_standard_envelope
990
- client = AiLite.new(api_key: "token-abc")
1054
+ client = provider.new(api_key: "token-abc")
991
1055
 
992
1056
  with_stubbed_http(FakeResponse.new("200", "not-json")) do |_captured, _response|
993
1057
  result = client.chat("Say hello")
@@ -1001,7 +1065,7 @@ class AiLiteTest < Minitest::Test
1001
1065
  end
1002
1066
 
1003
1067
  def test_debug_true_returns_raw_response_parse_error_body
1004
- client = AiLite.new(api_key: "token-abc")
1068
+ client = provider.new(api_key: "token-abc")
1005
1069
 
1006
1070
  with_stubbed_http(FakeResponse.new("200", "not-json")) do |_captured, _response|
1007
1071
  result = client.chat("Say hello", debug: true)
@@ -1014,7 +1078,7 @@ class AiLiteTest < Minitest::Test
1014
1078
  end
1015
1079
 
1016
1080
  def test_network_errors_return_standard_envelope
1017
- client = AiLite.new(api_key: "token-abc")
1081
+ client = provider.new(api_key: "token-abc")
1018
1082
  original_start = Net::HTTP.method(:start)
1019
1083
 
1020
1084
  Net::HTTP.singleton_class.send(:define_method, :start) do |_host, _port, use_ssl:, &_block|
@@ -1163,3 +1227,108 @@ class AiLiteTest < Minitest::Test
1163
1227
  Net::HTTP.singleton_class.send(:define_method, :start, original_start)
1164
1228
  end
1165
1229
  end
1230
+
1231
+ # Run the same HTTP and result contract against every legacy entry point.
1232
+ class AiLiteLegacyTest < AiLiteTest
1233
+ def provider
1234
+ AiLite
1235
+ end
1236
+ end
1237
+
1238
+ class AiLiteNamespaceTest < Minitest::Test
1239
+ def setup
1240
+ AiLite::OpenAI.reset_configuration!
1241
+ end
1242
+
1243
+ def test_provider_calls_are_quiet_and_constants_remain_compatible
1244
+ _stdout, stderr = capture_io do
1245
+ AiLite::OpenAI.configure { |config| config.api_key = "test-key" }
1246
+ assert_instance_of AiLite::OpenAI, AiLite::OpenAI.client
1247
+ AiLite::OpenAI.new
1248
+ end
1249
+
1250
+ assert_empty stderr
1251
+ assert_same AiLite::OpenAI::Configuration, AiLite::Configuration
1252
+ assert_equal AiLite::OpenAI::API_BASE_URL, AiLite::API_BASE_URL
1253
+ refute AiLite::OpenAI < AiLite
1254
+ end
1255
+
1256
+ def test_legacy_configuration_and_client_are_shared_with_openai
1257
+ capture_io do
1258
+ AiLite.configure { |config| config.api_key = "legacy-key" }
1259
+ assert_same AiLite.configuration, AiLite::OpenAI.configuration
1260
+ assert_same AiLite.client, AiLite::OpenAI.client
1261
+ assert_equal "legacy-key", AiLite::OpenAI.new.api_key
1262
+ assert_instance_of AiLite::OpenAI, AiLite.new
1263
+
1264
+ first_client = AiLite.client
1265
+ AiLite::OpenAI.configure { |config| config.api_key = "provider-key" }
1266
+ refute_same first_client, AiLite.client
1267
+ assert_equal "provider-key", AiLite.new.api_key
1268
+
1269
+ second_client = AiLite::OpenAI.client
1270
+ AiLite.reset_client!
1271
+ refute_same second_client, AiLite::OpenAI.client
1272
+ AiLite.reset_configuration!
1273
+ assert_nil AiLite::OpenAI.configuration.api_key
1274
+ end
1275
+ end
1276
+
1277
+ def test_each_legacy_entry_point_warns_only_once_and_preserves_arguments
1278
+ previous = AiLite.instance_variable_get(:@deprecated_entry_points)
1279
+ AiLite.instance_variable_set(:@deprecated_entry_points, {})
1280
+ methods = %i[new configuration configure reset_configuration! client reset_client!
1281
+ chat chat_stream moderate embed image speak transcribe
1282
+ available_models model_available? configured_models check_configured_models]
1283
+
1284
+ stdout, stderr = capture_io do
1285
+ methods.each do |name|
1286
+ test_case = self
1287
+ handler = lambda do |*args, **kwargs, &block|
1288
+ test_case.assert_equal ["input"], args
1289
+ test_case.assert_equal({ debug: true }, kwargs)
1290
+ test_case.assert_equal :block_result, block.call
1291
+ :result
1292
+ end
1293
+ next if name == :new
1294
+
1295
+ original = AiLite::OpenAI.method(name)
1296
+ begin
1297
+ AiLite::OpenAI.define_singleton_method(name, &handler)
1298
+ 2.times do
1299
+ assert_equal :result, AiLite.public_send(name, "input", debug: true) { :block_result }
1300
+ end
1301
+ ensure
1302
+ AiLite::OpenAI.define_singleton_method(name, original)
1303
+ end
1304
+ end
1305
+ 2.times { AiLite.new(api_key: "test-key") }
1306
+ end
1307
+
1308
+ assert_empty stdout
1309
+ assert_equal methods.length, stderr.lines.length
1310
+ methods.each do |name|
1311
+ assert_includes stderr, "AiLite.#{name} is deprecated. Use AiLite::OpenAI.#{name} instead."
1312
+ end
1313
+ assert_includes stderr, "removed in v2.0"
1314
+ ensure
1315
+ AiLite.instance_variable_set(:@deprecated_entry_points, previous)
1316
+ end
1317
+
1318
+ def test_provider_can_be_required_directly_in_a_fresh_process
1319
+ require "open3"
1320
+ require "rbconfig"
1321
+ script = <<~'CODE'
1322
+ require "ai_lite/openai"
1323
+ abort unless AiLite::OpenAI.new(api_key: "test-key").model == AiLite::OpenAI::DEFAULT_MODEL
1324
+ require "ai_lite"
1325
+ abort unless AiLite::Configuration.equal?(AiLite::OpenAI::Configuration)
1326
+ CODE
1327
+ stdout, stderr, status = Open3.capture3(
1328
+ RbConfig.ruby, "-I#{File.expand_path('../lib', __dir__)}", "-e", script
1329
+ )
1330
+ assert status.success?, stderr
1331
+ assert_empty stdout
1332
+ assert_empty stderr
1333
+ end
1334
+ end