ask-llm-providers 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/lib/ask/llm/openai_compatible.rb +101 -0
- data/lib/ask/llm/version.rb +1 -1
- data/lib/ask/provider/openai_compatible.rb +93 -0
- data/lib/ask-llm-providers.rb +12 -11
- metadata +3 -6
- data/lib/ask/provider/deepseek.rb +0 -47
- data/lib/ask/provider/mimo.rb +0 -30
- data/lib/ask/provider/opencode.rb +0 -30
- data/lib/ask/provider/opencode_go.rb +0 -30
- data/lib/ask/provider/openrouter.rb +0 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 82485a61da8048017c554844b7b31218778eca6c20a95a237331b62c2eb31da4
|
|
4
|
+
data.tar.gz: 66818f024528ecf1dde358b5a6e0882687b2e75c023d9c02659fa9c97116babb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ac4f1392b495866a46232a8d3b7b5ae00adf4db9e14cc05c774e3478a15f31f8d79313eebf110749d3972ea6f5b02ec33d90a536ec4dade0a8498c58b265ca45
|
|
7
|
+
data.tar.gz: 423dadd52ef3653cf8552b7fde1f9a551c793f0c9c1650af139ada68f415d22e90f9d76a1e6788fd2633f074af09439dbf8304168e3f017f7161a46627330327
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [0.6.0] — 2026-07-17
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **14 new OpenAI-compatible providers** — aiml, ai21, anyscale, deepinfra, featherless, friendli, github, hyperbolic, meta, nebius, novita, nscale, nvidia_nim, sambanova. Each is one line in the registry. Total OpenAI-compatible providers: 26. Total providers: 33.
|
|
6
|
+
- **Auto-generated tests** — `OpenAICompatibleTest` now builds its test list from `OPENAI_COMPATIBLE` dynamically. Adding a provider automatically generates 5 identity tests (registered, slug, capabilities, api_base, requires_api_key).
|
|
7
|
+
|
|
8
|
+
## [0.5.0] — 2026-07-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **OpenAI-compatible provider registry** (`Ask::LLM::OPENAI_COMPATIBLE`) — 12 providers defined as data, not classes. Adding a new OpenAI-compatible API (Groq, Together, Fireworks, etc.) is one line in the registry. No new file, no subclass.
|
|
13
|
+
- **`Ask::Providers::OpenAICompatible` class** — single class handling all registered providers by reading from the registry. Each provider gets an anonymous subclass with its slug, api_base, env var mapping, and quirks (reasoning_content, extra_headers) set from config.
|
|
14
|
+
- **Unified test** — `OpenAICompatibleTest` dynamically tests every registered provider: identity, slug, capabilities, api_base, env var resolution, request building, response parsing, streaming, and tool formatting.
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
|
|
18
|
+
- **5 subclass files** — `deepseek.rb`, `openrouter.rb`, `opencode.rb`, `opencode_go.rb`, `mimo.rb` deleted. Replaced by registry entries.
|
|
19
|
+
- **DeepSeek-specific test file** — covered by the unified test.
|
|
20
|
+
|
|
1
21
|
## [0.4.0] — 2026-07-16
|
|
2
22
|
|
|
3
23
|
### Added
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module LLM
|
|
5
|
+
# Registry of OpenAI-compatible providers (data, not classes).
|
|
6
|
+
#
|
|
7
|
+
# Each entry is configuration for {Ask::Providers::OpenAICompatible}.
|
|
8
|
+
# To add a new provider, add one line here — no new file, no subclass.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# groq: { api_base: "https://api.groq.com/openai/v1", api_key_env: "GROQ_API_KEY" }
|
|
12
|
+
#
|
|
13
|
+
OPENAI_COMPATIBLE = {
|
|
14
|
+
aiml: { api_base: "https://api.aimlapi.com/v1", api_key_env: "AIML_API_KEY",
|
|
15
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
16
|
+
|
|
17
|
+
ai21: { api_base: "https://api.ai21.com/studio/v1", api_key_env: "AI21_API_KEY",
|
|
18
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
19
|
+
|
|
20
|
+
anyscale: { api_base: "https://api.endpoints.anyscale.com/v1", api_key_env: "ANYSCALE_API_KEY",
|
|
21
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
22
|
+
|
|
23
|
+
cerebras: { api_base: "https://api.cerebras.ai/v1", api_key_env: "CEREBRAS_API_KEY",
|
|
24
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
25
|
+
|
|
26
|
+
deepinfra: { api_base: "https://api.deepinfra.com/v1/openai", api_key_env: "DEEPINFRA_API_KEY",
|
|
27
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
28
|
+
|
|
29
|
+
deepseek: { api_base: "https://api.deepseek.com", api_key_env: "DEEPSEEK_API_KEY",
|
|
30
|
+
reasoning_content: true,
|
|
31
|
+
capabilities: { chat: true, streaming: true, tool_calls: true, thinking: true } },
|
|
32
|
+
|
|
33
|
+
featherless: { api_base: "https://api.featherless.ai/v1", api_key_env: "FEATHERLESS_API_KEY",
|
|
34
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
35
|
+
|
|
36
|
+
fireworks: { api_base: "https://api.fireworks.ai/inference/v1", api_key_env: "FIREWORKS_API_KEY",
|
|
37
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
38
|
+
|
|
39
|
+
friendli: { api_base: "https://api.friendli.ai/serverless/v1", api_key_env: "FRIENDLI_API_KEY",
|
|
40
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
41
|
+
|
|
42
|
+
github: { api_base: "https://models.inference.ai.azure.com", api_key_env: "GITHUB_API_KEY",
|
|
43
|
+
capabilities: { chat: true, streaming: true, tool_calls: true, vision: true } },
|
|
44
|
+
|
|
45
|
+
groq: { api_base: "https://api.groq.com/openai/v1", api_key_env: "GROQ_API_KEY",
|
|
46
|
+
capabilities: { chat: true, streaming: true, tool_calls: true, vision: true } },
|
|
47
|
+
|
|
48
|
+
hyperbolic: { api_base: "https://api.hyperbolic.xyz/v1", api_key_env: "HYPERBOLIC_API_KEY",
|
|
49
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
50
|
+
|
|
51
|
+
meta: { api_base: "https://api.llama.com/compat/v1", api_key_env: "LLAMA_API_KEY",
|
|
52
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
53
|
+
|
|
54
|
+
mimo: { api_base: "https://token-plan-sgp.xiaomimimo.com/v1", api_key_env: "MIMO_API_KEY",
|
|
55
|
+
capabilities: { chat: true, streaming: true } },
|
|
56
|
+
|
|
57
|
+
moonshot: { api_base: "https://api.moonshot.ai/v1", api_key_env: "MOONSHOT_API_KEY",
|
|
58
|
+
capabilities: { chat: true, streaming: true } },
|
|
59
|
+
|
|
60
|
+
nebius: { api_base: "https://api.studio.nebius.ai/v1", api_key_env: "NEBIUS_API_KEY",
|
|
61
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
62
|
+
|
|
63
|
+
novita: { api_base: "https://api.novita.ai/v3/openai", api_key_env: "NOVITA_API_KEY",
|
|
64
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
65
|
+
|
|
66
|
+
nscale: { api_base: "https://inference.api.nscale.com/v1", api_key_env: "NSCALE_API_KEY",
|
|
67
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
68
|
+
|
|
69
|
+
nvidia_nim: { api_base: "https://integrate.api.nvidia.com/v1", api_key_env: "NVIDIA_NIM_API_KEY",
|
|
70
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
71
|
+
|
|
72
|
+
opencode: { api_base: "https://opencode.ai/zen/v1", api_key_env: "OPENCODE_API_KEY",
|
|
73
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
74
|
+
|
|
75
|
+
opencode_go: { api_base: "https://opencode.ai/zen/go/v1", api_key_env: "OPENCODE_GO_API_KEY",
|
|
76
|
+
alternate_env: "OPENCODE_API_KEY",
|
|
77
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
78
|
+
|
|
79
|
+
openrouter: { api_base: "https://openrouter.ai/api/v1", api_key_env: "OPENROUTER_API_KEY",
|
|
80
|
+
extra_headers: { "HTTP-Referer" => "https://github.com/ask-rb",
|
|
81
|
+
"X-Title" => "ask-rb" },
|
|
82
|
+
capabilities: { chat: true, streaming: true, tool_calls: true, vision: true,
|
|
83
|
+
thinking: true, structured_output: true } },
|
|
84
|
+
|
|
85
|
+
perplexity: { api_base: "https://api.perplexity.ai", api_key_env: "PERPLEXITY_API_KEY",
|
|
86
|
+
capabilities: { chat: true, streaming: true } },
|
|
87
|
+
|
|
88
|
+
sambanova: { api_base: "https://api.sambanova.ai/v1", api_key_env: "SAMBANOVA_API_KEY",
|
|
89
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
90
|
+
|
|
91
|
+
together: { api_base: "https://api.together.xyz/v1", api_key_env: "TOGETHER_API_KEY",
|
|
92
|
+
capabilities: { chat: true, streaming: true, tool_calls: true } },
|
|
93
|
+
|
|
94
|
+
xai: { api_base: "https://api.x.ai/v1", api_key_env: "XAI_API_KEY",
|
|
95
|
+
capabilities: { chat: true, streaming: true, tool_calls: true, vision: true,
|
|
96
|
+
thinking: true } }
|
|
97
|
+
}.freeze
|
|
98
|
+
|
|
99
|
+
OPENAI_COMPATIBLE_COUNT = OPENAI_COMPATIBLE.size
|
|
100
|
+
end
|
|
101
|
+
end
|
data/lib/ask/llm/version.rb
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module Providers
|
|
5
|
+
# Single class for all OpenAI-compatible providers.
|
|
6
|
+
#
|
|
7
|
+
# Reads its behavior from {Ask::LLM::OPENAI_COMPATIBLE} via the class-level
|
|
8
|
+
# +compat_config+ that gets set at registration time. Each registered
|
|
9
|
+
# provider is an anonymous subclass with its own compat_config.
|
|
10
|
+
#
|
|
11
|
+
# This eliminates one subclass file per provider — adding a new
|
|
12
|
+
# OpenAI-compatible API is a one-line config entry.
|
|
13
|
+
class OpenAICompatible < Ask::Providers::OpenAI
|
|
14
|
+
class << self
|
|
15
|
+
attr_reader :compat_config
|
|
16
|
+
|
|
17
|
+
def slug
|
|
18
|
+
compat_config[:slug].to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def capabilities
|
|
22
|
+
compat_config[:capabilities] || { chat: true, streaming: true, tool_calls: true }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def configuration_options
|
|
26
|
+
%i[api_key base_url]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def configuration_requirements
|
|
30
|
+
%i[api_key]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def configured?(config)
|
|
34
|
+
key = config.respond_to?(:api_key) ? config.api_key : nil
|
|
35
|
+
key ||= ENV[compat_config[:api_key_env].to_s]
|
|
36
|
+
key ||= ENV[compat_config[:alternate_env].to_s] if compat_config[:alternate_env]
|
|
37
|
+
key.to_s.length > 0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def assume_models_exist?
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize(config = {})
|
|
46
|
+
@compat_cfg = self.class.compat_config || {}
|
|
47
|
+
config = normalize_compat_config(config)
|
|
48
|
+
super(config)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def api_base
|
|
52
|
+
@config.base_url || @compat_cfg[:api_base] || super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def headers
|
|
56
|
+
h = { "Content-Type" => "application/json" }
|
|
57
|
+
key = @config.api_key
|
|
58
|
+
h["Authorization"] = "Bearer #{key}" if key
|
|
59
|
+
if (extra = @compat_cfg[:extra_headers])
|
|
60
|
+
extra.each { |k, v| h[k] = v }
|
|
61
|
+
end
|
|
62
|
+
h
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def format_messages(messages)
|
|
66
|
+
result = super
|
|
67
|
+
if @compat_cfg[:reasoning_content]
|
|
68
|
+
result.each { |fm| fm[:reasoning_content] ||= "" if fm[:role] == "assistant" && fm[:tool_calls] }
|
|
69
|
+
end
|
|
70
|
+
result
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def normalize_compat_config(config)
|
|
76
|
+
return config unless config.is_a?(Hash)
|
|
77
|
+
|
|
78
|
+
slug = self.class.slug
|
|
79
|
+
api_key = config[:api_key] || config["api_key"] ||
|
|
80
|
+
config[:"#{slug}_api_key"] ||
|
|
81
|
+
ENV[@compat_cfg[:api_key_env].to_s] ||
|
|
82
|
+
(ENV[@compat_cfg[:alternate_env].to_s] if @compat_cfg[:alternate_env]) ||
|
|
83
|
+
ENV["#{slug.upcase}_API_KEY"]
|
|
84
|
+
|
|
85
|
+
base_url = config[:base_url] || config["base_url"] ||
|
|
86
|
+
ENV["#{slug.upcase}_API_BASE"] ||
|
|
87
|
+
@compat_cfg[:api_base]
|
|
88
|
+
|
|
89
|
+
Ask::LLM::Config.new(api_key:, base_url:)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/ask-llm-providers.rb
CHANGED
|
@@ -17,21 +17,20 @@ require_relative "ask/llm/aliases"
|
|
|
17
17
|
# Provider transformation contract
|
|
18
18
|
require_relative "ask/llm/provider_config"
|
|
19
19
|
|
|
20
|
+
# OpenAI-compatible provider registry (data, not classes)
|
|
21
|
+
require_relative "ask/llm/openai_compatible"
|
|
22
|
+
|
|
20
23
|
# Load providers
|
|
21
24
|
require_relative "ask/provider/openai"
|
|
25
|
+
require_relative "ask/provider/openai_compatible"
|
|
22
26
|
require_relative "ask/provider/anthropic"
|
|
23
27
|
require_relative "ask/provider/google"
|
|
24
28
|
require_relative "ask/provider/bedrock"
|
|
25
29
|
require_relative "ask/provider/ollama"
|
|
26
30
|
require_relative "ask/provider/mistral"
|
|
27
31
|
require_relative "ask/provider/cloudflare"
|
|
28
|
-
require_relative "ask/provider/opencode"
|
|
29
|
-
require_relative "ask/provider/opencode_go"
|
|
30
|
-
require_relative "ask/provider/mimo"
|
|
31
|
-
require_relative "ask/provider/deepseek"
|
|
32
|
-
require_relative "ask/provider/openrouter"
|
|
33
32
|
|
|
34
|
-
# Register providers
|
|
33
|
+
# Register canonical providers
|
|
35
34
|
Ask::Provider.register(:openai, Ask::Providers::OpenAI)
|
|
36
35
|
Ask::Provider.register(:anthropic, Ask::Providers::Anthropic)
|
|
37
36
|
Ask::Provider.register(:gemini, Ask::Providers::Google)
|
|
@@ -39,11 +38,13 @@ Ask::Provider.register(:bedrock, Ask::Providers::Bedrock)
|
|
|
39
38
|
Ask::Provider.register(:ollama, Ask::Providers::Ollama)
|
|
40
39
|
Ask::Provider.register(:mistral, Ask::Providers::Mistral)
|
|
41
40
|
Ask::Provider.register(:cloudflare, Ask::Providers::Cloudflare)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Ask::
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
|
|
42
|
+
# Register OpenAI-compatible providers from the registry (data → classes)
|
|
43
|
+
Ask::LLM::OPENAI_COMPATIBLE.each do |name, cfg|
|
|
44
|
+
klass = Class.new(Ask::Providers::OpenAICompatible)
|
|
45
|
+
klass.define_singleton_method(:compat_config) { cfg.merge(slug: name.to_s) }
|
|
46
|
+
Ask::Provider.register(name, klass)
|
|
47
|
+
end
|
|
47
48
|
|
|
48
49
|
# Load bundled model catalog into Ask::ModelCatalog
|
|
49
50
|
Ask::LLM::Catalog.load!
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-llm-providers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -194,21 +194,18 @@ files:
|
|
|
194
194
|
- lib/ask/llm/models/opencode_go.json
|
|
195
195
|
- lib/ask/llm/models/openrouter.json
|
|
196
196
|
- lib/ask/llm/models_schema.json
|
|
197
|
+
- lib/ask/llm/openai_compatible.rb
|
|
197
198
|
- lib/ask/llm/provider_config.rb
|
|
198
199
|
- lib/ask/llm/sse_buffer.rb
|
|
199
200
|
- lib/ask/llm/version.rb
|
|
200
201
|
- lib/ask/provider/anthropic.rb
|
|
201
202
|
- lib/ask/provider/bedrock.rb
|
|
202
203
|
- lib/ask/provider/cloudflare.rb
|
|
203
|
-
- lib/ask/provider/deepseek.rb
|
|
204
204
|
- lib/ask/provider/google.rb
|
|
205
|
-
- lib/ask/provider/mimo.rb
|
|
206
205
|
- lib/ask/provider/mistral.rb
|
|
207
206
|
- lib/ask/provider/ollama.rb
|
|
208
207
|
- lib/ask/provider/openai.rb
|
|
209
|
-
- lib/ask/provider/
|
|
210
|
-
- lib/ask/provider/opencode_go.rb
|
|
211
|
-
- lib/ask/provider/openrouter.rb
|
|
208
|
+
- lib/ask/provider/openai_compatible.rb
|
|
212
209
|
- lib/ask/skills/providers.model_select/SKILL.md
|
|
213
210
|
homepage: https://github.com/ask-rb/ask-llm-providers
|
|
214
211
|
licenses:
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Providers
|
|
5
|
-
# DeepSeek API — an OpenAI-compatible provider at api.deepseek.com.
|
|
6
|
-
# Supports DeepSeek V2, V3, V4 Chat, R1 reasoning, and all DeepSeek models
|
|
7
|
-
# via the OpenAI-compatible endpoint.
|
|
8
|
-
#
|
|
9
|
-
# Configuration via environment:
|
|
10
|
-
# DEEPSEEK_API_KEY — required, your DeepSeek API key
|
|
11
|
-
# DEEPSEEK_API_BASE — optional, base URL (default: https://api.deepseek.com)
|
|
12
|
-
class DeepSeek < OpenAI
|
|
13
|
-
def api_base
|
|
14
|
-
@config.base_url || ENV["DEEPSEEK_API_BASE"] || "https://api.deepseek.com"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def headers
|
|
18
|
-
key = @config.api_key || ENV["DEEPSEEK_API_KEY"]
|
|
19
|
-
h = { "Content-Type" => "application/json" }
|
|
20
|
-
h["Authorization"] = "Bearer #{key}" if key
|
|
21
|
-
h
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# DeepSeek requires reasoning_content in every assistant message
|
|
25
|
-
# that includes tool_calls. Override format_messages to inject it.
|
|
26
|
-
def format_messages(messages)
|
|
27
|
-
super.map do |fm|
|
|
28
|
-
if fm[:role] == "assistant" && fm[:tool_calls]
|
|
29
|
-
fm[:reasoning_content] = fm[:reasoning_content] || ""
|
|
30
|
-
end
|
|
31
|
-
fm
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
class << self
|
|
36
|
-
def slug; "deepseek"; end
|
|
37
|
-
def configuration_options; %i[api_key base_url]; end
|
|
38
|
-
def configuration_requirements; %i[api_key]; end
|
|
39
|
-
def configured?(config)
|
|
40
|
-
key = config.respond_to?(:api_key) ? config.api_key : nil
|
|
41
|
-
key ||= ENV["DEEPSEEK_API_KEY"]
|
|
42
|
-
key.to_s.length > 0
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
data/lib/ask/provider/mimo.rb
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Providers
|
|
5
|
-
# Mimo API — an OpenAI-compatible provider
|
|
6
|
-
class Mimo < OpenAI
|
|
7
|
-
def api_base
|
|
8
|
-
@config.base_url || ENV["MIMO_API_BASE"] || "https://token-plan-sgp.xiaomimimo.com/v1"
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def headers
|
|
12
|
-
key = @config.api_key || ENV["MIMO_API_KEY"]
|
|
13
|
-
h = { "Content-Type" => "application/json" }
|
|
14
|
-
h["Authorization"] = "Bearer #{key}" if key
|
|
15
|
-
h
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
class << self
|
|
19
|
-
def slug; "mimo"; end
|
|
20
|
-
def configuration_options; %i[api_key base_url]; end
|
|
21
|
-
def configuration_requirements; %i[api_key]; end
|
|
22
|
-
def configured?(config)
|
|
23
|
-
key = config.respond_to?(:api_key) ? config.api_key : nil
|
|
24
|
-
key ||= ENV["MIMO_API_KEY"]
|
|
25
|
-
key.to_s.length > 0
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Providers
|
|
5
|
-
# OpenCode API — an OpenAI-compatible provider at opencode.ai
|
|
6
|
-
class OpenCode < OpenAI
|
|
7
|
-
def api_base
|
|
8
|
-
@config.base_url || ENV["OPENCODE_API_BASE"] || "https://opencode.ai/zen/v1"
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def headers
|
|
12
|
-
key = @config.api_key || ENV["OPENCODE_API_KEY"]
|
|
13
|
-
h = { "Content-Type" => "application/json" }
|
|
14
|
-
h["Authorization"] = "Bearer #{key}" if key
|
|
15
|
-
h
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
class << self
|
|
19
|
-
def slug; "opencode"; end
|
|
20
|
-
def configuration_options; %i[api_key base_url]; end
|
|
21
|
-
def configuration_requirements; %i[api_key]; end
|
|
22
|
-
def configured?(config)
|
|
23
|
-
key = config.respond_to?(:api_key) ? config.api_key : nil
|
|
24
|
-
key ||= ENV["OPENCODE_API_KEY"]
|
|
25
|
-
key.to_s.length > 0
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Providers
|
|
5
|
-
# OpenCode Go API — an OpenAI-compatible provider at opencode.ai/zen/go
|
|
6
|
-
class OpenCodeGo < OpenAI
|
|
7
|
-
def api_base
|
|
8
|
-
@config.base_url || ENV["OPENCODE_GO_API_BASE"] || "https://opencode.ai/zen/go/v1"
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def headers
|
|
12
|
-
key = @config.api_key || ENV["OPENCODE_API_KEY"] || ENV["OPENCODE_GO_API_KEY"]
|
|
13
|
-
h = { "Content-Type" => "application/json" }
|
|
14
|
-
h["Authorization"] = "Bearer #{key}" if key
|
|
15
|
-
h
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
class << self
|
|
19
|
-
def slug; "opencode_go"; end
|
|
20
|
-
def configuration_options; %i[api_key base_url]; end
|
|
21
|
-
def configuration_requirements; %i[api_key]; end
|
|
22
|
-
def configured?(config)
|
|
23
|
-
key = config.respond_to?(:api_key) ? config.api_key : nil
|
|
24
|
-
key ||= ENV["OPENCODE_API_KEY"] || ENV["OPENCODE_GO_API_KEY"]
|
|
25
|
-
key.to_s.length > 0
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ask
|
|
4
|
-
module Providers
|
|
5
|
-
# OpenRouter API — an OpenAI-compatible aggregator at openrouter.ai.
|
|
6
|
-
# Provides access to many models through a single endpoint.
|
|
7
|
-
#
|
|
8
|
-
# Configuration via environment:
|
|
9
|
-
# OPENROUTER_API_KEY — required, your OpenRouter API key
|
|
10
|
-
# OPENROUTER_API_BASE — optional, base URL (default: https://openrouter.ai/api/v1)
|
|
11
|
-
class OpenRouter < OpenAI
|
|
12
|
-
def api_base
|
|
13
|
-
@config.base_url || ENV["OPENROUTER_API_BASE"] || "https://openrouter.ai/api/v1"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def headers
|
|
17
|
-
h = super
|
|
18
|
-
key = @config.api_key || ENV["OPENROUTER_API_KEY"]
|
|
19
|
-
h["Authorization"] = "Bearer #{key}" if key
|
|
20
|
-
h["HTTP-Referer"] = ENV["OPENROUTER_REFERER"] || "https://github.com/ask-rb"
|
|
21
|
-
h["X-Title"] = ENV["OPENROUTER_APP_TITLE"] || "ask-rb"
|
|
22
|
-
h
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
class << self
|
|
26
|
-
def slug; "openrouter"; end
|
|
27
|
-
def configuration_options; %i[api_key base_url]; end
|
|
28
|
-
def configuration_requirements; %i[api_key]; end
|
|
29
|
-
def configured?(config)
|
|
30
|
-
key = config.respond_to?(:api_key) ? config.api_key : nil
|
|
31
|
-
key ||= ENV["OPENROUTER_API_KEY"]
|
|
32
|
-
key.to_s.length > 0
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|