refactor: make src/llm/ self-contained for crate extraction (#767)

* refactor: make src/llm/ self-contained for crate extraction

Move LlmError, LLM config types, and OAuth callback helpers into
src/llm/ so the module has zero `use crate::` imports outside of
crate::llm. This prepares the module for extraction into a standalone
workspace crate.

- Move LlmError enum from src/error.rs to src/llm/error.rs
- Move LlmConfig, NearAiConfig, RegistryProviderConfig, BedrockConfig,
  CacheRetention, OAUTH_PLACEHOLDER from src/config/llm.rs to
  src/llm/config.rs
- Move OAuth callback utilities (callback_url, bind_callback_listener,
  wait_for_callback, landing_html, etc.) from src/cli/oauth_defaults.rs
  to src/llm/oauth_helpers.rs
- Remove session.rs dependency on crate::bootstrap (inline default path)
- Add cache_retention field to RegistryProviderConfig, resolve from env
  in config/llm.rs instead of reading env var in llm/mod.rs
- Add Check 6 to scripts/check-boundaries.sh enforcing LLM isolation
- All original locations re-export for backward compatibility

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* style: fix formatting

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR #767 review — session path bug and boundary check

1. Fix SessionConfig::default() usage in setup wizard: the fallback at
   wizard.rs:995 now constructs SessionConfig with the real
   default_session_path() instead of a relative "session.json", which
   would write auth tokens to the CWD instead of ~/.ironclaw/.

2. Widen check-boundaries.sh Check 6 to catch all `crate::` references
   (not just `use crate::` imports). Pre-existing inline references
   (16 occurrences) are reported as warnings; only new `use crate::`
   imports are hard violations.

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR #767 review and audit findings in src/llm/

PR review fixes:
- Reject wildcard addresses (0.0.0.0, ::) in OAuth callback listener
  to prevent session token exposure on all interfaces
- Fix boundary check comment-stripping that could hide real violations
  (use sed to strip inline comments before matching)

Audit fixes:
- Fix UTF-8 byte-index slicing panic in recording.rs hint extraction
- Add effective_model_name() delegation to RetryProvider and
  SmartRoutingProvider for consistency with other wrappers
- Add calculate_cost() delegation to CachedProvider and RecordingLlm
- Deduplicate retry loop logic in RetryProvider via generic helper
- Replace hardcoded /tmp path in recording tests with tempfile

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-03-09 22:31:17 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 45923ef360
commit 14aadd3063
26 changed files with 871 additions and 701 deletions
+2 -57
View File
@@ -138,45 +138,8 @@ pub enum ChannelError {
HealthCheckFailed { name: String },
}
/// LLM provider errors.
#[derive(Debug, thiserror::Error)]
pub enum LlmError {
#[error("Provider {provider} request failed: {reason}")]
RequestFailed { provider: String, reason: String },
#[error("Provider {provider} rate limited, retry after {retry_after:?}")]
RateLimited {
provider: String,
retry_after: Option<Duration>,
},
#[error("Invalid response from {provider}: {reason}")]
InvalidResponse { provider: String, reason: String },
#[error("Context length exceeded: {used} tokens used, {limit} allowed")]
ContextLengthExceeded { used: usize, limit: usize },
#[error("Model {model} not available on provider {provider}")]
ModelNotAvailable { provider: String, model: String },
#[error("Authentication failed for provider {provider}")]
AuthFailed { provider: String },
#[error("Session expired for provider {provider}")]
SessionExpired { provider: String },
#[error("Session renewal failed for provider {provider}: {reason}")]
SessionRenewalFailed { provider: String, reason: String },
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
// LlmError lives in src/llm/error.rs; re-exported here for backward compatibility.
pub use crate::llm::error::LlmError;
/// Tool execution errors.
#[derive(Debug, thiserror::Error)]
@@ -486,24 +449,6 @@ mod tests {
);
}
#[test]
fn llm_error_display() {
let err = LlmError::ContextLengthExceeded {
used: 100_000,
limit: 50_000,
};
let msg = err.to_string();
assert!(msg.contains("100000"), "Should mention used tokens: {msg}");
assert!(msg.contains("50000"), "Should mention limit: {msg}");
let err = LlmError::RateLimited {
provider: "openai".to_string(),
retry_after: Some(Duration::from_secs(30)),
};
let msg = err.to_string();
assert!(msg.contains("openai"), "Should mention provider: {msg}");
}
#[test]
fn job_error_display() {
let err = JobError::MaxJobsExceeded { max: 5 };