rasti-ai 3.1.0 → 3.2.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.features/api-normalization/api-design.md +184 -0
  3. data/.features/api-normalization/overview.md +230 -0
  4. data/AGENTS.md +136 -19
  5. data/README.md +151 -19
  6. data/lib/rasti/ai/anthropic/assistant.rb +1 -11
  7. data/lib/rasti/ai/anthropic/client.rb +6 -28
  8. data/lib/rasti/ai/anthropic/provider.rb +88 -0
  9. data/lib/rasti/ai/assistant.rb +11 -12
  10. data/lib/rasti/ai/client.rb +13 -10
  11. data/lib/rasti/ai/errors.rb +8 -0
  12. data/lib/rasti/ai/gemini/assistant.rb +1 -11
  13. data/lib/rasti/ai/gemini/client.rb +2 -24
  14. data/lib/rasti/ai/gemini/provider.rb +91 -0
  15. data/lib/rasti/ai/huawei_maas/assistant.rb +0 -7
  16. data/lib/rasti/ai/huawei_maas/client.rb +0 -19
  17. data/lib/rasti/ai/huawei_maas/provider.rb +25 -0
  18. data/lib/rasti/ai/huawei_maas/roles.rb +1 -8
  19. data/lib/rasti/ai/open_ai/assistant.rb +1 -5
  20. data/lib/rasti/ai/open_ai/client.rb +3 -33
  21. data/lib/rasti/ai/open_ai/provider.rb +75 -0
  22. data/lib/rasti/ai/open_router/assistant.rb +0 -7
  23. data/lib/rasti/ai/open_router/client.rb +0 -19
  24. data/lib/rasti/ai/open_router/provider.rb +25 -0
  25. data/lib/rasti/ai/open_router/roles.rb +1 -8
  26. data/lib/rasti/ai/provider.rb +197 -0
  27. data/lib/rasti/ai/provider_aware.rb +17 -0
  28. data/lib/rasti/ai/result.rb +11 -0
  29. data/lib/rasti/ai/roles.rb +11 -0
  30. data/lib/rasti/ai/version.rb +1 -1
  31. data/lib/rasti/ai.rb +93 -0
  32. data/spec/anthropic/assistant_spec.rb +14 -0
  33. data/spec/anthropic/provider_spec.rb +121 -0
  34. data/spec/gemini/assistant_spec.rb +15 -0
  35. data/spec/gemini/provider_spec.rb +104 -0
  36. data/spec/open_ai/assistant_spec.rb +15 -0
  37. data/spec/open_ai/provider_spec.rb +111 -0
  38. data/spec/rasti_ai_spec.rb +322 -0
  39. data/spec/resources/open_ai/conversation_request.json +1 -0
  40. data/spec/resources/open_ai/system_request.json +1 -0
  41. data/tasks/assistant.rake +8 -8
  42. metadata +25 -2
@@ -0,0 +1,111 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::AI::OpenAI::Provider do
4
+
5
+ let(:question) { 'How many goals has Messi scored for Barca?' }
6
+
7
+ let(:answer) { 'Lionel Messi scored 672 goals in 778 official matches for FC Barcelona.' }
8
+
9
+ def stub_chat_completions(url, request_body)
10
+ stub_request(:post, url)
11
+ .with(body: JSON.dump(request_body))
12
+ .to_return(body: read_resource('open_ai/basic_response.json', content: answer))
13
+ end
14
+
15
+ def basic_body(model)
16
+ {
17
+ model: model,
18
+ messages: [
19
+ {
20
+ role: 'user',
21
+ content: question
22
+ }
23
+ ],
24
+ tools: [],
25
+ tool_choice: 'none'
26
+ }
27
+ end
28
+
29
+ it 'Name, model and classes' do
30
+ provider = Rasti::AI.provider :open_ai
31
+
32
+ assert_equal :open_ai, provider.name
33
+ assert_equal Rasti::AI.openai_default_model, provider.model
34
+ assert_instance_of Rasti::AI::OpenAI::Client, provider.build_client
35
+ assert_instance_of Rasti::AI::OpenAI::Assistant, provider.create_assistant
36
+ end
37
+
38
+ it 'Generate text' do
39
+ stub_chat_completions 'https://api.openai.com/v1/chat/completions', basic_body(Rasti::AI.openai_default_model)
40
+
41
+ result = Rasti::AI.provider(:open_ai).generate_text prompt: question
42
+
43
+ assert_equal answer, result.content
44
+ assert_equal 'open_ai', result.usage.provider
45
+ end
46
+
47
+ it 'Generate text with json schema and thinking' do
48
+ json_schema = {player: 'string'}
49
+
50
+ body = basic_body(Rasti::AI.openai_default_model).merge(
51
+ response_format: {
52
+ type: 'json_schema',
53
+ json_schema: json_schema
54
+ },
55
+ reasoning_effort: 'low'
56
+ )
57
+
58
+ stub_chat_completions 'https://api.openai.com/v1/chat/completions', body
59
+
60
+ result = Rasti::AI.provider(:open_ai).generate_text prompt: question, json_schema: json_schema, thinking: 'low'
61
+
62
+ assert_equal answer, result.content
63
+ end
64
+
65
+ it 'Generate text with thinking none' do
66
+ body = basic_body(Rasti::AI.openai_default_model).merge(reasoning_effort: 'none')
67
+
68
+ stub_chat_completions 'https://api.openai.com/v1/chat/completions', body
69
+
70
+ result = Rasti::AI.provider(:open_ai).generate_text prompt: question, thinking: 'none'
71
+
72
+ assert_equal answer, result.content
73
+ end
74
+
75
+ describe 'OpenAI compatible providers' do
76
+
77
+ it 'OpenRouter' do
78
+ provider = Rasti::AI.provider :open_router
79
+
80
+ assert_equal :open_router, provider.name
81
+ assert_equal Rasti::AI.openrouter_default_model, provider.model
82
+ assert_instance_of Rasti::AI::OpenRouter::Client, provider.build_client
83
+ assert_instance_of Rasti::AI::OpenRouter::Assistant, provider.create_assistant
84
+
85
+ stub_chat_completions 'https://openrouter.ai/api/v1/chat/completions', basic_body(Rasti::AI.openrouter_default_model)
86
+
87
+ result = provider.generate_text prompt: question
88
+
89
+ assert_equal answer, result.content
90
+ assert_equal 'open_router', result.usage.provider
91
+ end
92
+
93
+ it 'Huawei MaaS' do
94
+ provider = Rasti::AI.provider :huawei_maas
95
+
96
+ assert_equal :huawei_maas, provider.name
97
+ assert_equal Rasti::AI.huawei_maas_default_model, provider.model
98
+ assert_instance_of Rasti::AI::HuaweiMaaS::Client, provider.build_client
99
+ assert_instance_of Rasti::AI::HuaweiMaaS::Assistant, provider.create_assistant
100
+
101
+ stub_chat_completions 'https://api-ap-southeast-1.modelarts-maas.com/v2/chat/completions', basic_body(Rasti::AI.huawei_maas_default_model)
102
+
103
+ result = provider.generate_text prompt: question
104
+
105
+ assert_equal answer, result.content
106
+ assert_equal 'huawei_maas', result.usage.provider
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,322 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::AI do
4
+
5
+ let(:question) { 'How many goals has Messi scored for Barca?' }
6
+
7
+ let(:answer) { 'Lionel Messi scored 672 goals in 778 official matches for FC Barcelona.' }
8
+
9
+ def stub_open_ai(model:nil, prompt:nil, content:nil)
10
+ stub_request(:post, 'https://api.openai.com/v1/chat/completions')
11
+ .with(body: read_resource('open_ai/basic_request.json', model: model || Rasti::AI.openai_default_model, prompt: prompt || question))
12
+ .to_return(body: read_resource('open_ai/basic_response.json', content: content || answer))
13
+ end
14
+
15
+ def with_default_provider(provider)
16
+ previous = Rasti::AI.default_provider
17
+ Rasti::AI.default_provider = provider
18
+ yield
19
+ ensure
20
+ Rasti::AI.default_provider = previous
21
+ end
22
+
23
+ describe 'Create assistant' do
24
+
25
+ it 'Provider' do
26
+ assistant = Rasti::AI.create_assistant provider: :open_ai
27
+
28
+ assert_instance_of Rasti::AI::OpenAI::Assistant, assistant
29
+ end
30
+
31
+ it 'Provider alias' do
32
+ assistant = Rasti::AI.create_assistant provider: 'openai'
33
+
34
+ assert_instance_of Rasti::AI::OpenAI::Assistant, assistant
35
+ end
36
+
37
+ it 'Provider and model' do
38
+ assistant = Rasti::AI.create_assistant provider: :anthropic, model: 'claude-sonnet-4-5'
39
+
40
+ assert_instance_of Rasti::AI::Anthropic::Assistant, assistant
41
+ assert_equal 'claude-sonnet-4-5', assistant.model
42
+ end
43
+
44
+ it 'Provider and model as single string' do
45
+ assistant = Rasti::AI.create_assistant model: 'gemini:gemini-2.0-flash'
46
+
47
+ assert_instance_of Rasti::AI::Gemini::Assistant, assistant
48
+ assert_equal 'gemini-2.0-flash', assistant.model
49
+ end
50
+
51
+ it 'Default provider from config' do
52
+ with_default_provider :anthropic do
53
+ assistant = Rasti::AI.create_assistant
54
+
55
+ assert_instance_of Rasti::AI::Anthropic::Assistant, assistant
56
+ end
57
+ end
58
+
59
+ it 'Explicit provider takes precedence over config' do
60
+ with_default_provider :anthropic do
61
+ assistant = Rasti::AI.create_assistant provider: :gemini
62
+
63
+ assert_instance_of Rasti::AI::Gemini::Assistant, assistant
64
+ end
65
+ end
66
+
67
+ it 'Undefined provider' do
68
+ with_default_provider nil do
69
+ error = assert_raises ArgumentError do
70
+ Rasti::AI.create_assistant
71
+ end
72
+
73
+ assert_match /\AUndefined provider/, error.message
74
+ end
75
+ end
76
+
77
+ it 'Unknown provider' do
78
+ assert_raises Rasti::AI::Errors::UnknownProvider do
79
+ Rasti::AI.create_assistant provider: :invalid
80
+ end
81
+ end
82
+
83
+ it 'Thinking, tools and json schema' do
84
+ assistant = Rasti::AI.create_assistant provider: :open_ai,
85
+ thinking: 'high',
86
+ tools: [GoalsByPlayer.new],
87
+ json_schema: {player: 'string'}
88
+
89
+ assert_equal 'high', assistant.thinking
90
+ end
91
+
92
+ it 'System' do
93
+ assistant = Rasti::AI.create_assistant provider: :open_ai, system: 'Act as sports journalist'
94
+
95
+ assert_equal 'Act as sports journalist', assistant.state.context
96
+ end
97
+
98
+ it 'State' do
99
+ state = Rasti::AI::AssistantState.new context: 'Act as sports journalist'
100
+
101
+ assistant = Rasti::AI.create_assistant provider: :open_ai, state: state
102
+
103
+ assert_same state, assistant.state
104
+ end
105
+
106
+ it 'State and system are mutually exclusive' do
107
+ error = assert_raises ArgumentError do
108
+ Rasti::AI.create_assistant provider: :open_ai, state: Rasti::AI::AssistantState.new, system: 'Act as sports journalist'
109
+ end
110
+
111
+ assert_equal 'Use state or system, not both', error.message
112
+ end
113
+
114
+ it 'Custom client' do
115
+ client = Rasti::AI::OpenAI::Client.new
116
+
117
+ assistant = Rasti::AI.create_assistant provider: :open_ai, client: client
118
+
119
+ stub_open_ai
120
+
121
+ assert_equal answer, assistant.call(question)
122
+ end
123
+
124
+ it 'Client options' do
125
+ stub_open_ai
126
+
127
+ tracked_usage = []
128
+
129
+ assistant = Rasti::AI.create_assistant provider: :open_ai,
130
+ api_key: 'custom_api_key',
131
+ usage_tracker: ->(usage) { tracked_usage << usage },
132
+ http_read_timeout: 120,
133
+ http_max_retries: 5
134
+
135
+ assistant.call question
136
+
137
+ assert_requested :post, 'https://api.openai.com/v1/chat/completions',
138
+ headers: {'Authorization' => 'Bearer custom_api_key'}
139
+
140
+ assert_equal 1, tracked_usage.count
141
+ assert_equal 'open_ai', tracked_usage.first.provider
142
+ end
143
+
144
+ end
145
+
146
+ describe 'Provider' do
147
+
148
+ it 'Build from name' do
149
+ provider = Rasti::AI.provider :open_ai
150
+
151
+ assert_instance_of Rasti::AI::OpenAI::Provider, provider
152
+ assert_equal :open_ai, provider.name
153
+ assert_equal Rasti::AI.openai_default_model, provider.model
154
+ end
155
+
156
+ it 'Build from alias' do
157
+ assert_instance_of Rasti::AI::OpenRouter::Provider, Rasti::AI.provider('openrouter')
158
+ assert_instance_of Rasti::AI::HuaweiMaaS::Provider, Rasti::AI.provider(:huawei)
159
+ assert_instance_of Rasti::AI::OpenAI::Provider, Rasti::AI.provider(:OpenAI)
160
+ end
161
+
162
+ it 'Build from provider and model as single string' do
163
+ provider = Rasti::AI.provider model: 'anthropic:claude-sonnet-4-5'
164
+
165
+ assert_instance_of Rasti::AI::Anthropic::Provider, provider
166
+ assert_equal 'claude-sonnet-4-5', provider.model
167
+ end
168
+
169
+ it 'Names' do
170
+ assert_equal [:open_ai, :gemini, :anthropic, :open_router, :huawei_maas], Rasti::AI::Provider.names
171
+ end
172
+
173
+ it 'Unknown provider' do
174
+ error = assert_raises Rasti::AI::Errors::UnknownProvider do
175
+ Rasti::AI.provider :invalid
176
+ end
177
+
178
+ assert_equal 'Unknown provider :invalid. Valid providers: open_ai, gemini, anthropic, open_router, huawei_maas', error.message
179
+ end
180
+
181
+ it 'Builds client and assistant of its own provider' do
182
+ provider = Rasti::AI.provider :gemini
183
+
184
+ assert_instance_of Rasti::AI::Gemini::Client, provider.build_client
185
+ assert_instance_of Rasti::AI::Gemini::Assistant, provider.create_assistant
186
+ end
187
+
188
+ it 'Reusable instance' do
189
+ provider = Rasti::AI.provider :open_ai, api_key: 'tenant_api_key'
190
+
191
+ stub_open_ai
192
+
193
+ provider.generate_text prompt: question
194
+
195
+ assert_requested :post, 'https://api.openai.com/v1/chat/completions',
196
+ headers: {'Authorization' => 'Bearer tenant_api_key'}
197
+
198
+ assert_instance_of Rasti::AI::OpenAI::Assistant, provider.create_assistant
199
+ end
200
+
201
+ it 'Invalid thinking level' do
202
+ error = assert_raises ArgumentError do
203
+ Rasti::AI.provider(:open_ai).generate_text prompt: question, thinking: 'invalid'
204
+ end
205
+
206
+ assert_equal "Invalid thinking level 'invalid'. Valid: none, low, medium, high", error.message
207
+ end
208
+
209
+ end
210
+
211
+ describe 'Generate text' do
212
+
213
+ it 'Provider' do
214
+ stub_open_ai
215
+
216
+ result = Rasti::AI.generate_text prompt: question, provider: :open_ai
217
+
218
+ assert_instance_of Rasti::AI::Result, result
219
+ assert_equal answer, result.content
220
+ assert_equal 'open_ai', result.usage.provider
221
+ assert_equal 27, result.usage.input_tokens
222
+ assert_equal 229, result.usage.output_tokens
223
+ assert_equal 'stop', result.raw['choices'][0]['finish_reason']
224
+ end
225
+
226
+ it 'Provider and model' do
227
+ stub_open_ai model: 'gpt-4o'
228
+
229
+ result = Rasti::AI.generate_text prompt: question, provider: :open_ai, model: 'gpt-4o'
230
+
231
+ assert_equal answer, result.content
232
+ end
233
+
234
+ it 'Provider and model as single string' do
235
+ stub_open_ai model: 'gpt-4o'
236
+
237
+ result = Rasti::AI.generate_text prompt: question, model: 'open_ai:gpt-4o'
238
+
239
+ assert_equal answer, result.content
240
+ end
241
+
242
+ it 'Default provider from config' do
243
+ with_default_provider :open_ai do
244
+ stub_open_ai
245
+
246
+ assert_equal answer, Rasti::AI.generate_text(prompt: question).content
247
+ end
248
+ end
249
+
250
+ it 'Messages with generic roles' do
251
+ messages = [
252
+ {role: Rasti::AI::Roles::USER, content: 'who is the best player'},
253
+ {role: Rasti::AI::Roles::ASSISTANT, content: 'Lionel Messi'},
254
+ {role: Rasti::AI::Roles::USER, content: question}
255
+ ]
256
+
257
+ stub_request(:post, 'https://api.openai.com/v1/chat/completions')
258
+ .with(body: read_resource('open_ai/conversation_request.json', model: Rasti::AI.openai_default_model, prompt: question))
259
+ .to_return(body: read_resource('open_ai/basic_response.json', content: answer))
260
+
261
+ result = Rasti::AI.generate_text messages: messages, provider: :open_ai
262
+
263
+ assert_equal answer, result.content
264
+ end
265
+
266
+ it 'System message inside messages' do
267
+ messages = [
268
+ {role: Rasti::AI::Roles::SYSTEM, content: 'Act as sports journalist'},
269
+ {role: Rasti::AI::Roles::USER, content: question}
270
+ ]
271
+
272
+ stub_request(:post, 'https://api.openai.com/v1/chat/completions')
273
+ .with(body: read_resource('open_ai/system_request.json', model: Rasti::AI.openai_default_model, system: 'Act as sports journalist', prompt: question))
274
+ .to_return(body: read_resource('open_ai/basic_response.json', content: answer))
275
+
276
+ assert_equal answer, Rasti::AI.generate_text(messages: messages, provider: :open_ai).content
277
+ end
278
+
279
+ it 'Prompt and messages are mutually exclusive' do
280
+ error = assert_raises ArgumentError do
281
+ Rasti::AI.generate_text prompt: question, messages: [], provider: :open_ai
282
+ end
283
+
284
+ assert_equal 'Use prompt or messages, not both', error.message
285
+ end
286
+
287
+ it 'Undefined prompt and messages' do
288
+ error = assert_raises ArgumentError do
289
+ Rasti::AI.generate_text provider: :open_ai
290
+ end
291
+
292
+ assert_equal 'Undefined prompt or messages', error.message
293
+ end
294
+
295
+ it 'System' do
296
+ stub_request(:post, 'https://api.openai.com/v1/chat/completions')
297
+ .with(body: read_resource('open_ai/system_request.json', model: Rasti::AI.openai_default_model, system: 'Act as sports journalist', prompt: question))
298
+ .to_return(body: read_resource('open_ai/basic_response.json', content: answer))
299
+
300
+ result = Rasti::AI.generate_text prompt: question,
301
+ provider: :open_ai,
302
+ system: 'Act as sports journalist'
303
+
304
+ assert_equal answer, result.content
305
+ end
306
+
307
+ it 'Usage tracker' do
308
+ stub_open_ai
309
+
310
+ tracked_usage = []
311
+
312
+ result = Rasti::AI.generate_text prompt: question,
313
+ provider: :open_ai,
314
+ usage_tracker: ->(usage) { tracked_usage << usage }
315
+
316
+ assert_equal 1, tracked_usage.count
317
+ assert_equal tracked_usage.first.to_h, result.usage.to_h
318
+ end
319
+
320
+ end
321
+
322
+ end
@@ -0,0 +1 @@
1
+ {"model":"<%= model %>","messages":[{"role":"user","content":"who is the best player"},{"role":"assistant","content":"Lionel Messi"},{"role":"user","content":"<%= prompt %>"}],"tools":[],"tool_choice":"none"}
@@ -0,0 +1 @@
1
+ {"model":"<%= model %>","messages":[{"role":"system","content":"<%= system %>"},{"role":"user","content":"<%= prompt %>"}],"tools":[],"tool_choice":"none"}
data/tasks/assistant.rake CHANGED
@@ -19,11 +19,11 @@ require 'logger'
19
19
  WEATHER_MCP_URL = 'https://gateway.pipeworx.io/weather/mcp'.freeze
20
20
 
21
21
  PROVIDERS = {
22
- 'openai' => {key: 'OPENAI_API_KEY', klass: -> { Rasti::AI::OpenAI::Assistant }},
23
- 'gemini' => {key: 'GEMINI_API_KEY', klass: -> { Rasti::AI::Gemini::Assistant }},
24
- 'anthropic' => {key: 'ANTHROPIC_API_KEY', klass: -> { Rasti::AI::Anthropic::Assistant }},
25
- 'openrouter' => {key: 'OPENROUTER_API_KEY', klass: -> { Rasti::AI::OpenRouter::Assistant }},
26
- 'huawei_maas'=> {key: 'HUAWEI_MAAS_API_KEY', klass: -> { Rasti::AI::HuaweiMaaS::Assistant }}
22
+ 'openai' => {key: 'OPENAI_API_KEY', klass: -> { Rasti::AI::OpenAI::Assistant }, thinking: 'none'},
23
+ 'gemini' => {key: 'GEMINI_API_KEY', klass: -> { Rasti::AI::Gemini::Assistant }, thinking: 'none'},
24
+ 'anthropic' => {key: 'ANTHROPIC_API_KEY', klass: -> { Rasti::AI::Anthropic::Assistant }, thinking: 'none'},
25
+ 'openrouter' => {key: 'OPENROUTER_API_KEY', klass: -> { Rasti::AI::OpenRouter::Assistant }, thinking: 'none'},
26
+ 'huawei_maas' => {key: 'HUAWEI_MAAS_API_KEY', klass: -> { Rasti::AI::HuaweiMaaS::Assistant }}
27
27
  }.freeze
28
28
 
29
29
  def build_weather_mcp
@@ -70,7 +70,7 @@ def chat_loop(assistant)
70
70
  puts "\nGoodbye!"
71
71
  end
72
72
 
73
- def start_assistant(provider, env_key, assistant_klass)
73
+ def start_assistant(provider, env_key, assistant_klass, thinking)
74
74
  require 'rasti-ai'
75
75
 
76
76
  abort "[Error] #{env_key} is not set." unless ENV[env_key]
@@ -81,7 +81,7 @@ def start_assistant(provider, env_key, assistant_klass)
81
81
  mcp = build_weather_mcp
82
82
  print_banner provider.capitalize, mcp.list_tools.map { |t| t['name'] }
83
83
 
84
- chat_loop assistant_klass.call.new(mcp_servers: {weather: mcp})
84
+ chat_loop assistant_klass.call.new(mcp_servers: {weather: mcp}, thinking: thinking)
85
85
  end
86
86
 
87
87
  namespace :assistant do
@@ -89,7 +89,7 @@ namespace :assistant do
89
89
  PROVIDERS.each do |provider, config|
90
90
  desc "Chat with #{provider.capitalize} assistant using Pipeworx weather MCP (requires #{config[:key]})"
91
91
  task provider.to_sym do
92
- start_assistant(provider, config[:key], config[:klass])
92
+ start_assistant(provider, config[:key], config[:klass], config[:thinking])
93
93
  end
94
94
  end
95
95
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rasti-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Naiman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-27 00:00:00.000000000 Z
11
+ date: 2026-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_require
@@ -239,6 +239,8 @@ executables: []
239
239
  extensions: []
240
240
  extra_rdoc_files: []
241
241
  files:
242
+ - ".features/api-normalization/api-design.md"
243
+ - ".features/api-normalization/overview.md"
242
244
  - ".github/workflows/ci.yml"
243
245
  - ".gitignore"
244
246
  - ".ruby-gemset"
@@ -252,6 +254,7 @@ files:
252
254
  - lib/rasti/ai.rb
253
255
  - lib/rasti/ai/anthropic/assistant.rb
254
256
  - lib/rasti/ai/anthropic/client.rb
257
+ - lib/rasti/ai/anthropic/provider.rb
255
258
  - lib/rasti/ai/anthropic/roles.rb
256
259
  - lib/rasti/ai/assistant.rb
257
260
  - lib/rasti/ai/assistant_state.rb
@@ -259,9 +262,11 @@ files:
259
262
  - lib/rasti/ai/errors.rb
260
263
  - lib/rasti/ai/gemini/assistant.rb
261
264
  - lib/rasti/ai/gemini/client.rb
265
+ - lib/rasti/ai/gemini/provider.rb
262
266
  - lib/rasti/ai/gemini/roles.rb
263
267
  - lib/rasti/ai/huawei_maas/assistant.rb
264
268
  - lib/rasti/ai/huawei_maas/client.rb
269
+ - lib/rasti/ai/huawei_maas/provider.rb
265
270
  - lib/rasti/ai/huawei_maas/roles.rb
266
271
  - lib/rasti/ai/mcp/client.rb
267
272
  - lib/rasti/ai/mcp/constants.rb
@@ -269,10 +274,16 @@ files:
269
274
  - lib/rasti/ai/mcp/tools_registry.rb
270
275
  - lib/rasti/ai/open_ai/assistant.rb
271
276
  - lib/rasti/ai/open_ai/client.rb
277
+ - lib/rasti/ai/open_ai/provider.rb
272
278
  - lib/rasti/ai/open_ai/roles.rb
273
279
  - lib/rasti/ai/open_router/assistant.rb
274
280
  - lib/rasti/ai/open_router/client.rb
281
+ - lib/rasti/ai/open_router/provider.rb
275
282
  - lib/rasti/ai/open_router/roles.rb
283
+ - lib/rasti/ai/provider.rb
284
+ - lib/rasti/ai/provider_aware.rb
285
+ - lib/rasti/ai/result.rb
286
+ - lib/rasti/ai/roles.rb
276
287
  - lib/rasti/ai/tool.rb
277
288
  - lib/rasti/ai/tool_serializer.rb
278
289
  - lib/rasti/ai/usage.rb
@@ -281,9 +292,11 @@ files:
281
292
  - rasti-ai.gemspec
282
293
  - spec/anthropic/assistant_spec.rb
283
294
  - spec/anthropic/client_spec.rb
295
+ - spec/anthropic/provider_spec.rb
284
296
  - spec/coverage_helper.rb
285
297
  - spec/gemini/assistant_spec.rb
286
298
  - spec/gemini/client_spec.rb
299
+ - spec/gemini/provider_spec.rb
287
300
  - spec/huawei_maas/assistant_spec.rb
288
301
  - spec/huawei_maas/client_spec.rb
289
302
  - spec/mcp/client_spec.rb
@@ -292,8 +305,10 @@ files:
292
305
  - spec/minitest_helper.rb
293
306
  - spec/open_ai/assistant_spec.rb
294
307
  - spec/open_ai/client_spec.rb
308
+ - spec/open_ai/provider_spec.rb
295
309
  - spec/open_router/assistant_spec.rb
296
310
  - spec/open_router/client_spec.rb
311
+ - spec/rasti_ai_spec.rb
297
312
  - spec/resources/anthropic/basic_request.json
298
313
  - spec/resources/anthropic/basic_response.json
299
314
  - spec/resources/anthropic/tool_request.json
@@ -304,6 +319,8 @@ files:
304
319
  - spec/resources/gemini/tool_response.json
305
320
  - spec/resources/open_ai/basic_request.json
306
321
  - spec/resources/open_ai/basic_response.json
322
+ - spec/resources/open_ai/conversation_request.json
323
+ - spec/resources/open_ai/system_request.json
307
324
  - spec/resources/open_ai/tool_request.json
308
325
  - spec/resources/open_ai/tool_response.json
309
326
  - spec/support/helpers/erb.rb
@@ -336,9 +353,11 @@ summary: AI for apps
336
353
  test_files:
337
354
  - spec/anthropic/assistant_spec.rb
338
355
  - spec/anthropic/client_spec.rb
356
+ - spec/anthropic/provider_spec.rb
339
357
  - spec/coverage_helper.rb
340
358
  - spec/gemini/assistant_spec.rb
341
359
  - spec/gemini/client_spec.rb
360
+ - spec/gemini/provider_spec.rb
342
361
  - spec/huawei_maas/assistant_spec.rb
343
362
  - spec/huawei_maas/client_spec.rb
344
363
  - spec/mcp/client_spec.rb
@@ -347,8 +366,10 @@ test_files:
347
366
  - spec/minitest_helper.rb
348
367
  - spec/open_ai/assistant_spec.rb
349
368
  - spec/open_ai/client_spec.rb
369
+ - spec/open_ai/provider_spec.rb
350
370
  - spec/open_router/assistant_spec.rb
351
371
  - spec/open_router/client_spec.rb
372
+ - spec/rasti_ai_spec.rb
352
373
  - spec/resources/anthropic/basic_request.json
353
374
  - spec/resources/anthropic/basic_response.json
354
375
  - spec/resources/anthropic/tool_request.json
@@ -359,6 +380,8 @@ test_files:
359
380
  - spec/resources/gemini/tool_response.json
360
381
  - spec/resources/open_ai/basic_request.json
361
382
  - spec/resources/open_ai/basic_response.json
383
+ - spec/resources/open_ai/conversation_request.json
384
+ - spec/resources/open_ai/system_request.json
362
385
  - spec/resources/open_ai/tool_request.json
363
386
  - spec/resources/open_ai/tool_response.json
364
387
  - spec/support/helpers/erb.rb