Add option to change the base_url of openai provider#58
Conversation
📝 WalkthroughWalkthroughAdded an optional Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant CLI
participant Config
participant Main
participant Provider
participant ExternalAPI
User->>CLI: run with optional `-u/--base-url`
CLI->>Config: build(cli)
Config->>Main: return LumenConfig (includes base_url)
Main->>Provider: LumenProvider::new(provider, api_key, model, base_url)
Provider->>Provider: validate base_url for Customopenai
Provider->>ExternalAPI: construct endpoint (owned String) and connect
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
src/provider/mod.rs (1)
75-111: 💡 Verification agent🧩 Analysis chain
Consider extending base_url support to other applicable providers.
While the OpenAI implementation is correct, other providers like Ollama (which often runs locally) and OpenRouter (which provides multiple endpoints) could also benefit from configurable base URLs. This would make the feature more comprehensive and the API more consistent.
Consider extending base_url support to providers that could benefit from it:
ProviderType::Ollama => { let model = model.ok_or(LumenError::MissingModel("Ollama".to_string()))?; - let config = OllamaConfig::new(model); + let config = OllamaConfig::new(model, base_url); let provider = LumenProvider::Ollama(Box::new(OllamaProvider::new(client, config))); Ok(provider) }Let me check which other providers could benefit from configurable base URLs:
🏁 Script executed:
#!/bin/bash # Check the constructor signatures of other provider configs to see if they support base_url fd -e rs . src/provider/ --exec grep -l "Config::new" {} \; | xargs grep -A 5 "pub fn new"Length of output: 277
#!/bin/bash # List each provider config file and its `new` constructor signature grep -R -lE 'struct [A-Za-z0-9]+Config' src/provider | while read -r file; do echo "File: $file" grep -R -n 'pub fn new' "$file" echo done
Extend base_url support to all providers
Currently only the OpenAI provider respects a custom
base_url. To keep the API consistent, addbase_url: Option<String>to each provider’s config and pass it through insrc/provider/mod.rs.Providers to update:
- PhindConfig::new(model)
- GroqConfig::new(api_key, model)
- ClaudeConfig::new(api_key, model)
- OllamaConfig::new(model)
- OpenRouterConfig::new(api_key, model)
- DeepSeekConfig::new(api_key, model)
Example diff in src/provider/mod.rs:
ProviderType::Ollama => { let model = model.ok_or(LumenError::MissingModel("Ollama".to_string()))?; - let config = OllamaConfig::new(model); + let config = OllamaConfig::new(model, base_url.clone()); let provider = LumenProvider::Ollama(Box::new(OllamaProvider::new(client, config))); Ok(provider) }Repeat the same pattern for Phind, Groq, Claude, OpenRouter, and DeepSeek—updating each
Config::newsignature and constructor call to accept and forwardbase_url.
🧹 Nitpick comments (4)
src/config/cli.rs (1)
24-25: Consider adding provider-specific documentation to the help text.The implementation is correct, but the help text should clarify that this option currently only affects the OpenAI provider to set user expectations properly.
Consider updating the help documentation:
- #[arg(short = 'u', long = "base-url")] + #[arg(short = 'u', long = "base-url", help = "Custom base URL for OpenAI provider")]src/main.rs (1)
36-36: Consider adding URL validation for the base_url parameter.The implementation correctly passes the base_url to the provider constructor. However, consider validating that the base_url is a properly formatted URL before initialization to provide better error messages to users.
Consider adding URL validation:
// Before line 35, add validation if base_url is present if let Some(ref url) = config.base_url { if url.parse::<reqwest::Url>().is_err() { return Err(LumenError::InvalidArguments( format!("Invalid base URL format: {}", url) )); } }src/provider/mod.rs (1)
66-66: Consider documenting that base_url only affects OpenAI provider.The method signature accepts base_url for all provider types, but the parameter is currently only used by the OpenAI provider. Consider adding documentation to clarify this limitation.
Add documentation to the method:
impl LumenProvider { + /// Creates a new LumenProvider instance. + /// Note: base_url parameter currently only affects OpenAI provider. pub fn new(src/config/configuration.rs (1)
27-28: LGTM! Consider adding URL validation.The field follows the established pattern for optional configuration fields. Consider adding URL validation to ensure the provided base_url is well-formed.
You could add validation in the
buildmethod:let base_url = cli.base_url.clone().or(config.base_url); + + // Validate base_url if provided + if let Some(ref url) = base_url { + if url::Url::parse(url).is_err() { + return Err(LumenError::InvalidConfiguration( + format!("Invalid base_url: {}", url) + )); + } + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/config/cli.rs(1 hunks)src/config/configuration.rs(4 hunks)src/main.rs(1 hunks)src/provider/mod.rs(1 hunks)src/provider/openai.rs(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/provider/mod.rs (1)
src/provider/openai.rs (2)
new(15-21)new(30-32)
🔇 Additional comments (5)
src/provider/openai.rs (1)
15-21: LGTM! Clean implementation with appropriate fallback.The implementation correctly handles the optional base_url parameter while maintaining backward compatibility with the default OpenAI endpoint. The use of
unwrap_or_elsewith a closure is appropriate here.src/provider/mod.rs (1)
69-74: LGTM! OpenAI provider correctly receives the base_url parameter.The implementation properly passes the base_url parameter to the OpenAI configuration, enabling custom endpoint support for OpenAI-compatible APIs.
src/config/configuration.rs (3)
85-87: LGTM! Environment variable naming is consistent.The function follows the same pattern as other default functions and uses appropriate environment variable naming that aligns with the existing
LUMEN_*convention.
126-126: LGTM! CLI override logic is correct.The precedence logic correctly implements CLI args > config file > environment variable, which matches the expected behavior and is consistent with other configuration fields.
132-132: LGTM! Struct field integration is complete.The base_url field is correctly included in both the
buildmethod result and theDefaultimplementation, ensuring consistency across all configuration paths.Also applies to: 158-158
|
Hi, I know it's been a while but I like your change! Do you want to look at the conflicts? |
622c659 to
69fd560
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/provider/mod.rs (1)
51-119: Unresolved merge conflict markers must be resolved.The code contains Git merge conflict markers (
<<<<<<< HEAD,|||||||,=======,>>>>>>>) which will cause compilation failure.Key observations about the conflict:
- HEAD branch: Contains OpenRouter/Vercel custom provider logic using
ServiceTargetResolver- Merged branch: Contains OpenAI-specific path using
OpenAIConfig,OpenAIProvider, andLumenProvider::OpenAIenum variantThe merged branch references types (
OpenAIConfig,OpenAIProvider,LumenProvider::OpenAI,client) that don't align with the currentProviderBackendenum structure in HEAD. You'll need to:
- Resolve the conflict by integrating both the OpenRouter/Vercel logic and the OpenAI base_url support
- Decide whether to use the
ProviderBackend::GenAIpattern or introduce a separateOpenAIvariant- Ensure the
base_urlparameter is utilized for OpenAI provider configuration
🤖 Fix all issues with AI agents
In @src/config/configuration.rs:
- Around line 162-167: Resolve the leftover merge conflict markers in the
Default implementation in configuration.rs by removing the conflict noise
(<<<<<<<, =======, >>>>>>>) and returning both fields: set theme: None and
base_url: default_base_url() inside the Default::default() (or impl Default for
Configuration) so the struct initializes with both the theme and base_url fields
present; keep the call to default_base_url() as the value for base_url.
In @src/main.rs:
- Around line 37-45: Resolve the merge conflict by removing the conflict markers
and the undefined `client` reference; call LumenProvider::new with the correct
parameters from `config` to match its signature
(provider::LumenProvider::new(config.provider, config.api_key, config.model,
config.base_url)?), ensuring you reference the existing `config` struct fields
and the LumenProvider::new function rather than a non-existent `client`.
In @src/provider/openai.rs:
- Around line 1-5: Add the missing AIProvider trait and the two missing
ProviderError variants so the OpenAI implementation compiles: define an
AIProvider trait whose async method signatures match the impl in openai.rs (add
the same function names, argument types and return Result<..., ProviderError>
used in that impl), and extend the existing ProviderError enum by adding
UnexpectedResponse and APIError variants (use suitable payloads, e.g.,
UnexpectedResponse(String) and APIError(String) or boxed error types) so
references to ProviderError::UnexpectedResponse and ProviderError::APIError
resolve.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/config/cli.rssrc/config/configuration.rssrc/main.rssrc/provider/mod.rssrc/provider/openai.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/config/cli.rs
🧰 Additional context used
🧬 Code graph analysis (1)
src/provider/mod.rs (1)
src/provider/openai.rs (2)
new(15-21)new(30-32)
🔇 Additional comments (6)
src/provider/mod.rs (1)
49-49: LGTM on the new parameter.The
base_urlparameter is correctly added to the function signature.src/config/configuration.rs (3)
27-29: LGTM on the new field.The
base_urlfield is correctly added with appropriate serde attribute for environment variable fallback.
88-90: LGTM on the helper function.The
default_base_url()function follows the same pattern as other default functions (default_model,default_api_key).
129-135: LGTM on the configuration merging logic.The
base_urlis correctly merged with CLI taking precedence over config file, consistent with other options.src/provider/openai.rs (2)
7-22: LGTM on OpenAIConfig implementation.The configuration struct correctly handles optional
modelandbase_urlwith sensible defaults. The default modelgpt-4o-miniand default endpoint are appropriate.
89-97: LGTM on AIProvider trait implementation.The trait implementation correctly delegates to the internal
completemethod and exposes the model name.
69fd560 to
b1d271c
Compare
|
I updated the code to adapt to the new implementation structure The env var to set the api key is CUSTOMOPENAI_API_KEY, for example the parameters for GLM-4.7 on z.ai will be: -p customopenai -m glm-4.7 -u https://api.z.ai/api/coding/paas/v4/ |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @src/provider/mod.rs:
- Around line 49-50: The code currently only validates and uses base_url when
ProviderType::Customopenai is selected, which silently ignores a provided
base_url for other providers; update the provider initialization (the function
handling base_url in src/provider/mod.rs that returns Result<Self, LumenError>)
to check if base_url.is_some() while the selected provider is not
ProviderType::Customopenai and either return a clear error (preferred) or emit a
warning log indicating base_url will be ignored; apply the same check/behavior
in the other initialization branch around the logic at the region covering lines
67-77 so users aren’t confused when passing base_url for non-custom providers.
🧹 Nitpick comments (3)
src/config/providers.rs (1)
83-89: Consider a more generic default model name or add documentation about model configuration.The default model
"gpt-5-mini"is OpenAI-specific and will likely be invalid for most custom endpoints (e.g., local LLMs, other OpenAI-compatible providers). Users will need to explicitly set the model via CLI or config, but there's no indication of this requirement.Consider either:
- Using a more generic placeholder like
"default"or"gpt-3.5-turbo"(more widely supported), or- Adding a comment or documentation noting that users must configure the model when using custom endpoints.
src/provider/mod.rs (2)
69-69: Minor grammar improvement in error message.The phrase "required on CustomOpenAI provider" sounds slightly off.
✏️ Suggested improvement
- LumenError::ConfigurationError("A Custom URL is required on CustomOpenAI provider".to_string()) + LumenError::ConfigurationError("A custom URL is required for the CustomOpenAI provider".to_string())
56-77: Consider refactoring to reduce code duplication.The three match arms for
Openrouter,Vercel, andCustomopenaifollow a very similar structure. Consider extracting the common logic into a helper function or using a more data-driven approach.♻️ Example refactor approach
// Helper function to create config fn get_custom_provider_config( provider_type: ProviderType, base_url: Option<String>, defaults: &ProviderInfo, ) -> Result<CustomProviderConfig, LumenError> { let endpoint = match provider_type { ProviderType::Openrouter => "https://openrouter.ai/api/v1/".to_string(), ProviderType::Vercel => "https://ai-gateway.vercel.sh/v1/".to_string(), ProviderType::Customopenai => base_url.ok_or_else(|| { LumenError::ConfigurationError( "A custom URL is required for the CustomOpenAI provider".to_string() ) })?, _ => unreachable!(), }; Ok(CustomProviderConfig { endpoint, env_key: defaults.env_key, adapter_kind: AdapterKind::OpenAI, }) } // Then in the match arm: let config = get_custom_provider_config(provider_type, base_url, defaults)?;
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/config/cli.rssrc/config/configuration.rssrc/config/providers.rssrc/main.rssrc/provider/mod.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main.rs
- src/config/cli.rs
🧰 Additional context used
🧬 Code graph analysis (1)
src/provider/mod.rs (1)
src/config/providers.rs (1)
for_provider(94-99)
🔇 Additional comments (2)
src/config/configuration.rs (1)
27-28: LGTM! Clean implementation following existing patterns.The
base_urlfield integration follows the same pattern as other config fields (api_key,model), with proper precedence handling (CLI > config file > environment variable) and serde defaults.Also applies to: 88-90, 129-129, 135-135, 163-163
src/provider/mod.rs (1)
97-97: Consider the performance impact of cloning endpoint on every service target resolution.The
endpoint.clone()is called each time the resolver callback is invoked. While this is a real operation, in practice the cost is likely negligible compared to network I/O for API calls. However, if profiling reveals this as a bottleneck in high-frequency scenarios, consider usingArc<String>to avoid the clone overhead.
| base_url: Option<String>, | ||
| ) -> Result<Self, LumenError> { |
There was a problem hiding this comment.
Clarify base_url handling for non-Custom providers.
Currently, base_url is only validated and used when ProviderType::Customopenai is selected. If a user mistakenly passes base_url via CLI or environment variable while using a different provider (e.g., OpenAI, Groq), it will be silently ignored. This could cause confusion.
Consider either:
- Adding validation to return an error if
base_urlis provided for non-custom providers, or - Adding a warning log when
base_urlis provided but ignored.
Also applies to: 67-77
🤖 Prompt for AI Agents
In @src/provider/mod.rs around lines 49 - 50, The code currently only validates
and uses base_url when ProviderType::Customopenai is selected, which silently
ignores a provided base_url for other providers; update the provider
initialization (the function handling base_url in src/provider/mod.rs that
returns Result<Self, LumenError>) to check if base_url.is_some() while the
selected provider is not ProviderType::Customopenai and either return a clear
error (preferred) or emit a warning log indicating base_url will be ignored;
apply the same check/behavior in the other initialization branch around the
logic at the region covering lines 67-77 so users aren’t confused when passing
base_url for non-custom providers.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @src/provider/mod.rs:
- Around line 51-60: The check that rejects base_url for non-CustomOpenAI
providers needs an additional early validation step: when provider_type is
ProviderType::Customopenai and base_url.is_some(), attempt to parse the base_url
with reqwest::Url::parse() and if parsing fails return
LumenError::ConfigurationError with a clear message including the parse error;
update the branch around the existing base_url/is_some() check (the same block
referencing ProviderType::Customopenai and LumenError::ConfigurationError) to
perform this parse and error handling before proceeding.
- Line 108: The code calls Endpoint::from_owned (which doesn't exist in genai
0.4.0); replace it with Endpoint::from_static and supply a &'static str instead
of an owned String—either use a compile-time &'static str for the endpoint or
convert the owned String to a &'static str (e.g., leak it with
Box::leak(endpoint.into_boxed_str())) before calling
Endpoint::from_static(endpoint_static) where the variable referenced is the same
endpoint used in Endpoint::from_owned.
🧹 Nitpick comments (2)
src/provider/mod.rs (2)
78-88: Consider handling trailing slashes for consistency.The code comment on line 73 notes that Vercel requires a trailing slash for URL joining. However, the custom
base_urldoesn't enforce or normalize trailing slashes, which could lead to URL joining issues depending on how the genai library constructs request paths.Consider normalizing the endpoint URL to ensure consistent behavior.
♻️ Proposed trailing slash normalization
ProviderType::Customopenai => { - let endpoint = base_url.ok_or_else(|| { + let mut endpoint = base_url.ok_or_else(|| { LumenError::ConfigurationError("A Custom URL is required on CustomOpenAI provider".to_string()) })?; + + // Ensure trailing slash for consistent URL joining + if !endpoint.ends_with('/') { + endpoint.push('/'); + } CustomProviderConfig { endpoint, env_key: defaults.env_key, adapter_kind: AdapterKind::OpenAI, } }
79-81: Optional: Improve error message wording.The error message "A Custom URL is required on CustomOpenAI provider" could be clearer.
♻️ Suggested improvement
- LumenError::ConfigurationError("A Custom URL is required on CustomOpenAI provider".to_string()) + LumenError::ConfigurationError("Base URL is required when using the CustomOpenAI provider".to_string())
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/provider/mod.rs
🔇 Additional comments (1)
src/provider/mod.rs (1)
39-39: LGTM!The type change from
&'static strtoStringis necessary and correct for supporting dynamic endpoints from configuration.
| if (base_url.is_some()) | ||
| && !matches!( | ||
| provider_type, | ||
| ProviderType::Customopenai | ||
| ) | ||
| { | ||
| return Err(LumenError::ConfigurationError( | ||
| "Base URL can only be set for CustomOpenAI provider".to_string(), | ||
| )); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the actual code in src/provider/mod.rs around lines 51-60
head -70 src/provider/mod.rs | tail -40Repository: jnsahaj/lumen
Length of output: 1493
🏁 Script executed:
# Check dependencies in Cargo.toml to see if reqwest is available
cat Cargo.toml | grep -A 30 "\[dependencies\]"Repository: jnsahaj/lumen
Length of output: 902
🏁 Script executed:
# Search for how base_url is used throughout the codebase
rg -n "base_url" --type rustRepository: jnsahaj/lumen
Length of output: 865
🏁 Script executed:
# Look for genai library usage and Endpoint initialization
rg -n "genai\|Endpoint" --type rust -C 3Repository: jnsahaj/lumen
Length of output: 39
🏁 Script executed:
# Let's see more context around line 79 where base_url is used
sed -n '75,95p' src/provider/mod.rsRepository: jnsahaj/lumen
Length of output: 979
🏁 Script executed:
# Search for endpoint usage and how it flows through the code
rg -n "endpoint" --type rust src/provider/mod.rs -C 2Repository: jnsahaj/lumen
Length of output: 2338
🏁 Script executed:
# Check if there's URL validation elsewhere in the codebase
rg -n "Url::parse|parse.*url" --type rust -iRepository: jnsahaj/lumen
Length of output: 118
🏁 Script executed:
# Look at the full new() function to understand the flow
sed -n '47,100p' src/provider/mod.rsRepository: jnsahaj/lumen
Length of output: 2496
🏁 Script executed:
# Search for how Endpoint is handled and used
rg -n "Endpoint::|from_owned|http" --type rust -C 2 | head -50Repository: jnsahaj/lumen
Length of output: 2936
🏁 Script executed:
# Check if there's any error handling around the ServiceTargetResolver usage
rg -n "ServiceTargetResolver|ServiceTarget" --type rust -A 3 | head -80Repository: jnsahaj/lumen
Length of output: 1474
🏁 Script executed:
# Look at the command diff module that mentions URL parsing
sed -n '40,50p' src/command/diff/mod.rsRepository: jnsahaj/lumen
Length of output: 630
🏁 Script executed:
# Search for any HTTP request code that might fail on malformed URLs
rg -n "http|request|client\.post|client\.get" --type rust | head -20Repository: jnsahaj/lumen
Length of output: 885
Add URL validation for base_url when using CustomOpenAI provider.
The code currently accepts base_url as any string without validating its format. While the genai library will eventually validate it, providing early validation would improve the user experience by surfacing format errors immediately rather than during the HTTP request.
Consider adding validation using reqwest::Url::parse():
Suggested validation
ProviderType::Customopenai => {
let endpoint = base_url.ok_or_else(|| {
LumenError::ConfigurationError("A Custom URL is required on CustomOpenAI provider".to_string())
})?;
+
+ // Validate URL format
+ reqwest::Url::parse(&endpoint).map_err(|e| {
+ LumenError::ConfigurationError(format!("Invalid base URL: {}", e))
+ })?;
CustomProviderConfig {
endpoint,
env_key: defaults.env_key,
adapter_kind: AdapterKind::OpenAI,
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/provider/mod.rs around lines 51 - 60, The check that rejects base_url
for non-CustomOpenAI providers needs an additional early validation step: when
provider_type is ProviderType::Customopenai and base_url.is_some(), attempt to
parse the base_url with reqwest::Url::parse() and if parsing fails return
LumenError::ConfigurationError with a clear message including the parse error;
update the branch around the existing base_url/is_some() check (the same block
referencing ProviderType::Customopenai and LumenError::ConfigurationError) to
perform this parse and error handling before proceeding.
| let ServiceTarget { model, .. } = service_target; | ||
| Ok(ServiceTarget { | ||
| endpoint: Endpoint::from_static(endpoint), | ||
| endpoint: Endpoint::from_owned(endpoint.clone()), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -name "Cargo.toml" -type f 2>/dev/null | head -5Repository: jnsahaj/lumen
Length of output: 39
🏁 Script executed:
cat Cargo.toml | grep -A 5 "genai"Repository: jnsahaj/lumen
Length of output: 187
🏁 Script executed:
rg "Endpoint::from_owned|Endpoint::from_static" --type rustRepository: jnsahaj/lumen
Length of output: 154
🌐 Web query:
genai crate Rust Endpoint::from_owned version 0.4.0
💡 Result:
I couldn't find an Endpoint::from_owned in genai v0.4.0 (or in the genai v0.4.x docs/source). The genai docs and repo (v0.4.x) list Client, ClientConfig, ServiceTarget/ModelIden, etc., but no Endpoint::from_owned symbol. See the genai docs and repo here: [1][2].
Do you mean a different crate or symbol (e.g., an Endpoint type from another crate) or would you like me to search the genai repo source for a similarly named constructor?
Sources:
- genai docs (latest v0.4.x): https://docs.rs/genai/latest/ (summary). [1]
- genai GitHub repo: https://github.com/jeremychone/rust-genai. [2]
🏁 Script executed:
rg "use.*Endpoint|from genai" src/provider/mod.rs -B 5 -A 5Repository: jnsahaj/lumen
Length of output: 427
🏁 Script executed:
rg "struct Endpoint|impl Endpoint" --type rustRepository: jnsahaj/lumen
Length of output: 39
🏁 Script executed:
head -30 src/provider/mod.rsRepository: jnsahaj/lumen
Length of output: 955
🏁 Script executed:
sed -n '100,120p' src/provider/mod.rsRepository: jnsahaj/lumen
Length of output: 980
🌐 Web query:
genai rust crate 0.4.0 Endpoint API methods available
💡 Result:
Do you mean the genai crate's "Endpoint" (ServiceTarget/ServiceTargetResolver) type and its methods, or the available provider API endpoints (chat, embed, images, etc.) exposed by genai v0.4.0?
🌐 Web query:
site:docs.rs genai 0.4 Endpoint from_static from_owned resolver
💡 Result:
The genai v0.4 docs and examples use Endpoint::from_static(...) when you build a ServiceTarget inside a ServiceTargetResolver (example: examples/c06-target-resolver.rs). [1][2]
Note: genai’s example uses an Endpoint type with from_static; some other crates (e.g., xai-grpc-client) provide similar constructors (from_static and from_shared / from_owned-style APIs) on their Endpoint types — check the specific Endpoint implementation you’re using if you need a from_owned/from_shared variant. [3]
Sources:
- genai crate docs and examples (ServiceTargetResolver example). [1]
- genai crate module index. [2]
- xai-grpc-client Endpoint (from_static / from_shared). [3]
🌐 Web query:
genai rust resolver Endpoint methods 0.4.0 site:github.com/jeremychone/rust-genai
💡 Result:
The rust-genai v0.4.x release lets you override endpoint/auth via a ServiceTargetResolver (see README) — example usage is in examples/c06-target-resolver.rs. See: [1], [2].
Sources:
[1] jeremychone/rust-genai README (v0.4.x notes). (github.com)
[2] examples/c06-target-resolver.rs (custom endpoint/auth resolver example). (github.com)
Citations:
- 1: https://github.com/jeremychone/rust-genai
- 2: https://github.com/jeremychone/rust-genai/blob/main/examples/c06-target-resolver.rs
🏁 Script executed:
# Check if there's any documentation or comments about Endpoint in the repo
rg "Endpoint" src/provider/mod.rs -B 3 -A 3Repository: jnsahaj/lumen
Length of output: 769
🏁 Script executed:
# Look for any previous usage or version history comments
git log --oneline -n 20 src/provider/mod.rs 2>/dev/null || echo "Git history not available"Repository: jnsahaj/lumen
Length of output: 128
🏁 Script executed:
sed -n '90,115p' src/provider/mod.rsRepository: jnsahaj/lumen
Length of output: 1219
🏁 Script executed:
rg "config.endpoint" src/ -B 5 -A 5 --type rust | head -40Repository: jnsahaj/lumen
Length of output: 815
🌐 Web query:
genai 0.4.0 Endpoint::from_static signature &str string
💡 Result:
If you mean tonic::transport::Endpoint::from_static, its signature takes a &'static str (not String): pub fn from_static(s: &'static str) -> Self. [1]
For the genai crate (v0.4.x) there isn't a genai::Endpoint::from_static in the public docs — genai uses its own ServiceTarget/Client types; check genai docs for the exact types you’re working with. [2]
Sources:
- tonic::transport::Endpoint::from_static signature. [1] https://prisma.github.io/prisma-engines/doc/tonic/transport/struct.Endpoint.html
- genai crate docs (v0.4.x) — crate overview and types. [2] https://docs.rs/genai/latest/genai/
🏁 Script executed:
# Try to find what methods Endpoint actually has
python3 << 'EOF'
import urllib.request
import json
# Check docs.rs API for genai 0.4.0 Endpoint
try:
response = urllib.request.urlopen('https://docs.rs/genai/0.4.0/genai/resolver/struct.Endpoint.json')
data = json.loads(response.read().decode())
# Print methods if available
if 'docs' in str(data):
print(json.dumps(data, indent=2)[:1000])
except Exception as e:
print(f"Could not fetch: {e}")
EOFRepository: jnsahaj/lumen
Length of output: 143
🌐 Web query:
genai 0.4.0 resolver module Endpoint type definition methods
💡 Result:
In genai v0.4.0 (Go package) the resolver Endpoint type is a simple struct added in v0.4.0:
type Endpoint struct {
Name string json:"name,omitempty"
DeployedModelID string json:"deployedModelId,omitempty"
}
It represents an endpoint where models are deployed and has no receiver methods defined on it (just the two fields). [1]
Sources:
[1] google.golang.org/genai (Endpoint type, added in v0.4.0).
Endpoint::from_owned does not exist in genai 0.4.0 and will cause a compilation error.
The genai v0.4.0 resolver module provides only Endpoint::from_static, not from_owned. This change references a non-existent method and will prevent the code from compiling. Additionally, from_static expects a &'static str, not an owned String, which may require a different approach for dynamic endpoint configuration.
🤖 Prompt for AI Agents
In @src/provider/mod.rs at line 108, The code calls Endpoint::from_owned (which
doesn't exist in genai 0.4.0); replace it with Endpoint::from_static and supply
a &'static str instead of an owned String—either use a compile-time &'static str
for the endpoint or convert the owned String to a &'static str (e.g., leak it
with Box::leak(endpoint.into_boxed_str())) before calling
Endpoint::from_static(endpoint_static) where the variable referenced is the same
endpoint used in Endpoint::from_owned.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the exact genai version being used
echo "=== Cargo.toml genai dependency ==="
cat Cargo.toml | grep -A 2 "genai"
echo -e "\n=== Cargo.lock genai version ==="
cat Cargo.lock | grep -A 5 "name = \"genai\"" | head -10
echo -e "\n=== Check if from_owned is used anywhere ==="
rg "from_owned" --type rust -C 2
echo -e "\n=== Check genai resolver imports ==="
rg "use genai::resolver" --type rust -A 3
Length of output: 1118
🌐 Web query:
genai rust crate 0.4.4 Endpoint from_owned method
💡 Result:
There is no Endpoint::from_owned in genai v0.4.4. Use Endpoint::from_static (or other Endpoint constructors shown in the resolver docs/examples) — see the genai example that uses Endpoint::from_static. [1][2]
Sources:
- genai example using Endpoint::from_static. [1]
- genai crate docs (resolver/ServiceTarget/Client docs). [2]
🏁 Script executed:
#!/bin/bash
# Clone and check the genai repository directly to see if from_owned exists
cd /tmp
rm -rf rust-genai 2>/dev/null
git clone --depth 1 --branch v0.4.4 https://github.com/jeremychone/rust-genai.git 2>&1 || \
git clone --depth 1 https://github.com/jeremychone/rust-genai.git 2>&1
cd rust-genai
echo "=== Searching for from_owned in genai source ==="
rg "from_owned" -A 3 -B 3
echo -e "\n=== Searching for Endpoint implementation ==="
rg "impl.*Endpoint" -A 10
echo -e "\n=== Check Endpoint struct definition ==="
rg "struct Endpoint|pub fn from_" src/ -A 3
Length of output: 11163
@kiwnix You're absolutely right, and I apologize for the incorrect review comment.
I've verified the genai v0.4.4 source code, and Endpoint::from_owned does exist. It's defined in the resolver module and accepts anything that implements Into<Arc<str>>, which makes it perfect for your use case with dynamic endpoints.
pub fn from_owned(url: impl Into<Arc<str>>) -> Self {
Endpoint {
inner: EndpointInner::Owned(url.into()),
}
}Your implementation using Endpoint::from_owned(endpoint.clone()) is correct and will compile successfully. My initial analysis was based on incomplete information. The change from from_static to from_owned is the right approach for supporting dynamic base URLs.
Sorry for the confusion!
🧬 Code Graph Analysis Results
Filename: src/command/mod.rs (lines 43-45)
pub fn new(provider: LumenProvider) -> Self {
LumenCommand { provider }
}
Filename: src/ai_prompt.rs (lines 18-97)
Summary:
- Function: build_explain_prompt(command: &ExplainCommand) -> Result<Self, AIPromptError>
- Parameters:
- command: &ExplainCommand
- Returns:
- Ok(AIPrompt) containing system_prompt and user_prompt configured to explain Git changes concisely
- Err(AIPromptError) on error
- Key behavior:
- Builds a system_prompt describing the assistant role for explaining changes concisely
- Constructs user_prompt based on command.git_entity (Commit or Diff) with appropriate content
- Uses formatting to present change context and a focused explanation
- Error handling:
- Propagates AIPromptError on failure
- Important implementation details:
- For Commit, includes commit message and diffs
- For Diff, includes Changes diff block
- Keeps prompts concise with markdown formatting
Filename: src/ai_prompt.rs (lines 99-149)
Summary:
- Function: build_draft_prompt(command: &DraftCommand) -> Result<Self, AIPromptError>
- Parameters:
- command: &DraftCommand
- Returns:
- Ok(AIPrompt) for drafting a commit message
- Err(AIPromptError) if not a working-tree diff
- Key behavior:
- Validates that the command.git_entity is a Diff::WorkingTree
- Sets system_prompt describing a commit message generator with rules (present tense, concise, format: type(scope): message)
- Builds a user_prompt requesting a concise git commit message for the given diff, with a character limit and a type chosen from a JSON mapping
- Error handling:
- Returns AIPromptError if draft is not applicable to the provided diff
- Important implementation details:
- May include optional context in the prompt
- Enforces strict output format to be directly usable as a commit message
Filename: src/ai_prompt.rs (lines 151-170)
Summary:
- Function: build_operate_prompt(query: &str) -> Result<Self, AIPromptError>
- Parameters:
- query: &str
- Returns:
- Ok(AIPrompt) containing system_prompt and user_prompt for generating a Git command
- Err(AIPromptError) on error
- Key behavior:
- System prompt defines role as a Git assistant that provides commands with explanations
- User prompt asks to generate a Git command for the given query, including a command tag, explanation, and a warning for destructive commands
- Error handling:
- Propagates AIPromptError on failure
- Important implementation details:
- Prefixes any destructive commands with a warning field
- Intended to produce structured output suitable for execution and explanation
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/provider/mod.rs (1)
66-71: Fix type mismatch: missing.to_string()for OpencodeZen endpoint.The
endpointfield inCustomProviderConfigis now of typeString(line 39), but line 68 assigns a string literal without converting it. This will cause a compilation error.🐛 Proposed fix
ProviderType::OpencodeZen => CustomProviderConfig { - endpoint: "https://opencode.ai/zen/v1/", + endpoint: "https://opencode.ai/zen/v1/".to_string(), env_key: defaults.env_key, adapter_kind: AdapterKind::OpenAI, },
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/config/cli.rssrc/config/providers.rssrc/provider/mod.rs
🔇 Additional comments (8)
src/config/cli.rs (3)
37-38: LGTM!The CLI argument for base URL is properly defined with appropriate short and long flags. This integrates cleanly with the existing CLI structure.
56-56: LGTM!The
Customopenaivariant is properly added to theProviderTypeenum, maintaining consistency with the existing provider types.
74-74: LGTM!The string-to-enum mapping for "customopenai" is correctly implemented in the
FromStrtrait, following the pattern of existing providers.src/provider/mod.rs (4)
39-39: LGTM!Changing the
endpointfield toStringenables dynamic base URL configuration for custom providers. This is necessary to support the custom OpenAI provider feature.
49-60: LGTM!The validation ensures that
base_urlcan only be provided when using theCustomopenaiprovider. This prevents misuse and provides a clear error message when users attempt to set a base URL for standard providers.
83-93: LGTM!The
Customopenaiprovider correctly requires a base URL and returns a clear error if it's missing. The implementation properly constructs theCustomProviderConfigwith the provided endpoint.
113-113: LGTM!The change from
Endpoint::from_statictoEndpoint::from_ownedwithclone()is necessary since endpoints are now ownedStringvalues rather than static string slices. This aligns with the dynamic base URL feature.src/config/providers.rs (1)
90-96: No changes needed. The default model "gpt-5-mini" is a valid OpenAI model announced in August 2025 and remains available through 2026.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/provider/mod.rs (1)
109-118: Performance concern: endpoint cloned on every request.The resolver closure clones
endpoint(aString) each time it's invoked, which happens for every API request through theServiceTargetResolver. This is inefficient for frequently-used providers.♻️ Use Arc to avoid repeated cloning
At line 106, wrap
endpointinArcbefore the closure:- let endpoint = config.endpoint; + let endpoint = std::sync::Arc::new(config.endpoint); let adapter_kind = config.adapter_kind; let target_resolver = ServiceTargetResolver::from_resolver_fn( move |service_target: ServiceTarget| -> Result<ServiceTarget, genai::resolver::Error> { let ServiceTarget { model, .. } = service_target; Ok(ServiceTarget { - endpoint: Endpoint::from_owned(endpoint.clone()), + endpoint: Endpoint::from_owned((*endpoint).clone()), auth: AuthData::from_env(auth_env_key), model: ModelIden::new(adapter_kind, model.model_name), }) }, );
Arc::clone()(inside the move) is a cheap operation (just incrementing a refcount), whileString::clone()allocates new memory. SinceEndpoint::from_ownedacceptsimpl Into<Arc<str>>, the Arc-wrapped String will be efficiently converted.
🤖 Fix all issues with AI agents
In @src/provider/mod.rs:
- Around line 83-93: The error thrown when base_url is missing in the
ProviderType::Customopenai arm uses awkward wording; update the
LumenError::ConfigurationError message to clearly state that a custom endpoint
URL is required for the CustomOpenAI provider (e.g. "A custom endpoint URL is
required for the CustomOpenAI provider" or similar), keeping the existing
base_url check and construction of CustomProviderConfig (endpoint,
defaults.env_key, AdapterKind::OpenAI) unchanged.
🧹 Nitpick comments (2)
src/provider/mod.rs (2)
39-39: Consider performance: keep static endpoints for non-dynamic providers.The change from
&'static strtoStringforces all providers to allocate owned strings, even those with static endpoints (OpencodeZen, Openrouter, Vercel). Only Customopenai requires a dynamic endpoint.♻️ Proposed optimization using enum
struct CustomProviderConfig { - endpoint: String, + endpoint: EndpointSource, env_key: &'static str, adapter_kind: AdapterKind, } + +enum EndpointSource { + Static(&'static str), + Dynamic(String), +}Then in the resolver closure (around line 113), match on the endpoint source:
endpoint: match &endpoint { EndpointSource::Static(s) => Endpoint::from_static(s), EndpointSource::Dynamic(s) => Endpoint::from_owned(s.clone()), },This avoids unnecessary allocations for static endpoints.
51-60: Consider simplifying the validation logic.The validation correctly enforces that
base_urlis only used with Customopenai, but the double negative (!matches!) reduces readability.♻️ Optional: More direct validation
- if (base_url.is_some()) - && !matches!( - provider_type, - ProviderType::Customopenai - ) - { + if base_url.is_some() && !matches!(provider_type, ProviderType::Customopenai) { return Err(LumenError::ConfigurationError( "Base URL can only be set for CustomOpenAI provider".to_string(), )); }Or, if you prefer positive logic:
- if (base_url.is_some()) - && !matches!( - provider_type, - ProviderType::Customopenai - ) - { + if let Some(_) = base_url { + if !matches!(provider_type, ProviderType::Customopenai) { return Err(LumenError::ConfigurationError( "Base URL can only be set for CustomOpenAI provider".to_string(), )); + } }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/provider/mod.rs
🧰 Additional context used
🧬 Code graph analysis (1)
src/provider/mod.rs (1)
src/config/providers.rs (1)
for_provider(101-106)
🔇 Additional comments (3)
src/provider/mod.rs (3)
49-49: LGTM: New parameter properly integrated.The
base_urlparameter is correctly added and used throughout the function for validation and the Customopenai provider initialization.
64-64: LGTM: Customopenai correctly added to custom endpoint providers.The match pattern properly includes the new provider type alongside other custom endpoint providers.
68-82: These conversions follow from the endpoint type change.The
.to_string()calls on lines 68, 73, and 79 are necessary given the type change at line 39. See the earlier comment about optimizing the endpoint field type to avoid these allocations.
| ProviderType::Customopenai => { | ||
| let endpoint = base_url.ok_or_else(|| { | ||
| LumenError::ConfigurationError("A Custom URL is required on CustomOpenAI provider".to_string()) | ||
| })?; | ||
|
|
||
| CustomProviderConfig { | ||
| endpoint, | ||
| env_key: defaults.env_key, | ||
| adapter_kind: AdapterKind::OpenAI, | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, verify if CUSTOMOPENAI_API_KEY is defined
rg -n "CUSTOMOPENAI_API_KEY" src/Repository: jnsahaj/lumen
Length of output: 124
🏁 Script executed:
# Check the context of lines 83-93 in src/provider/mod.rs
sed -n '75,100p' src/provider/mod.rsRepository: jnsahaj/lumen
Length of output: 1298
🏁 Script executed:
# Look for defaults definition and how env_key is set
rg -n "defaults\." src/provider/mod.rs | head -20Repository: jnsahaj/lumen
Length of output: 707
🏁 Script executed:
# Search for env_key configuration in providers.rs
rg -n "env_key" src/config/providers.rs -B 2 -A 2Repository: jnsahaj/lumen
Length of output: 1932
Improve error message wording.
The environment variable configuration is correctly set up with CUSTOMOPENAI_API_KEY in the provider defaults. The only issue is the error message phrasing.
📝 Suggested error message improvement
- LumenError::ConfigurationError("A Custom URL is required on CustomOpenAI provider".to_string())
+ LumenError::ConfigurationError("A custom base URL is required for the CustomOpenAI provider".to_string())📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ProviderType::Customopenai => { | |
| let endpoint = base_url.ok_or_else(|| { | |
| LumenError::ConfigurationError("A Custom URL is required on CustomOpenAI provider".to_string()) | |
| })?; | |
| CustomProviderConfig { | |
| endpoint, | |
| env_key: defaults.env_key, | |
| adapter_kind: AdapterKind::OpenAI, | |
| } | |
| } | |
| ProviderType::Customopenai => { | |
| let endpoint = base_url.ok_or_else(|| { | |
| LumenError::ConfigurationError("A custom base URL is required for the CustomOpenAI provider".to_string()) | |
| })?; | |
| CustomProviderConfig { | |
| endpoint, | |
| env_key: defaults.env_key, | |
| adapter_kind: AdapterKind::OpenAI, | |
| } | |
| } |
🤖 Prompt for AI Agents
In @src/provider/mod.rs around lines 83 - 93, The error thrown when base_url is
missing in the ProviderType::Customopenai arm uses awkward wording; update the
LumenError::ConfigurationError message to clearly state that a custom endpoint
URL is required for the CustomOpenAI provider (e.g. "A custom endpoint URL is
required for the CustomOpenAI provider" or similar), keeping the existing
base_url check and construction of CustomProviderConfig (endpoint,
defaults.env_key, AdapterKind::OpenAI) unchanged.
|
@jnsahaj is this OK to merge? |
This PR Adds an option to change the openai base URL (for use in proxys or local llm with OpenAI-style API support).
The cli option is -u
The env is LUMEN_BASE_URL
Sorry i'm not proficient in rust so not sure about correct coding conventions or style.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.