6.8 KiB
Phase 1 Step 16a — Implementation Notes
Status
Done.
Plan reference
- Parent plan:
docs/implementation/PHASE-1-STEP-16-NOTES.md - Sub-phase goal: "Build
AppConfig::from_configabsorbing env/wrap/docs/user-agent/model-resolution logic"
Summary
Introduced AppConfig::from_config(config: Config) -> Result<AppConfig>
as the canonical constructor for a fully-initialized AppConfig. The
new constructor chains the four mutation methods (load_envs,
set_wrap, setup_document_loaders, setup_user_agent) plus a new
resolve_model() method that picks a default model when model_id
is empty.
The existing bridge (Config::init + Config::to_app_config) is
untouched. from_config is currently dead code (gated with
#[allow(dead_code)]) — it becomes the entry point in Step 16e when
main.rs switches over. The methods it calls (load_envs, etc.) are
no longer dead code because they're reachable via from_config, so
their #[allow(dead_code)] gates were removed.
What was changed
src/config/app_config.rs
Added:
AppConfig::from_config(config) -> Result<Self>— canonical constructor that copies serde fields, applies env overrides, validates wrap, installs doc loaders, resolves user agent, and ensures a default model.AppConfig::resolve_model(&mut self) -> Result<()>— ifmodel_idis empty, picks the first available chat model. Errors if no models are available. Replaces the logic fromConfig::setup_modelthat belongs onAppConfig(theModel-resolution half ofConfig::setup_modelstays in Config for now — that moves in 16e).- 8 unit tests covering field copying, doc loader insertion, user
agent resolution, wrap validation (valid + invalid), and
resolve_modelerror/happy paths.
Removed #[allow(dead_code)] from:
set_wrapsetup_document_loaderssetup_user_agentload_envs
These are now reachable via from_config. They remain pub because
REPL-mode mutations (via .set wrap <value> or similar) will go
through them once RequestContext stops mutating Config.
Removed entirely:
AppConfig::ensure_default_model_id— redundant with the newresolve_model. Had no callers outside itself (confirmed via grep).
Behavior parity notes
-
from_configis non-destructive: it consumesConfigby value (not&Config) since post-bridge, Config is no longer needed. This matches the long-term design. -
from_configvsto_app_config+ mutations: The methods called insidefrom_configare identical bodies to the ones currently called onConfiginsideConfig::init. Env var reads, wrap validation, doc loader defaults, and user agent resolution all produce the same state. -
resolve_modelvsConfig::setup_model:Config::setup_modeldoes TWO things: (a) ensuremodel_idis non-empty (pick default if empty) (b) resolve theModelstruct viaModel::retrieve_modeland store it inself.modelAppConfig::resolve_modelonly does (a).- (b) happens today in
cfg.set_model(&model_id)insideConfig::setup_model. In the new architecture, theModelstruct lives onRequestContext.model, andModel::retrieve_model(&app_config, &app_config.model_id, ...)will be called insideRequestContext::new(or equivalent) once the bridge is removed in 16e.
Files modified
src/config/app_config.rs— 2 new methods, 4#[allow(dead_code)]gates removed, 1 method deleted, 8 new tests.
Files NOT modified
src/config/mod.rs—Config::initstill runs all mutations on Config; bridge still copies to AppConfig. Unchanged in 16a.src/config/bridge.rs— Untouched. Used byfrom_configinternally (config.to_app_config()).src/main.rs— Still uses the bridge flow. Switch happens in 16e.
Assumptions made
-
from_configconsumesConfigby value (not&Config) — aligns with the long-term design whereConfigis discarded after conversion. No current caller would benefit from keeping the Config around after conversion. -
resolve_modelnarrow scope: only responsible for ensuringmodel_idis non-empty. Does NOT resolve aModelstruct — that's RequestContext's job. This matches the split betweenAppConfig(the configuration) andRequestContext(the resolved runtime handle). -
#[allow(dead_code)]onfrom_configandresolve_model: they're unused until 16e. The gate is explicit so grep-hunts can find them when 16e switches over. -
User agent prefix in tests: I assumed the user agent prefix is not critical to test literally (it depends on the crate name). The test checks for a non-"auto" value containing
/rather than matchingloki-ai/. Safer against crate rename.
Open questions (parked for later sub-phases)
-
Should
from_configalso run secret interpolation? CurrentlyConfig::initdoes a two-pass YAML parse where the raw content gets secrets injected from the vault, then the Config is re-parsed. In the new architecture this belongs inmain.rsor a separate helper (the Config comes in already-interpolated). Not a 16a concern. -
Test naming convention: Existing tests use
fn test_name_returns_value_when_condition. New tests usefn from_config_does_thing. Both styles present in the file; kept consistent with new code. -
ensure_default_model_iddeletion: confirmed via grep that it had no callers outside itself. Deleted cleanly. If a future sub-phase needs the Option return variant, it can be re-added.
Verification
cargo check— clean, zero warningscargo clippy— clean, zero warningscargo test— 122 passing (114 pre-16a + 8 new), zero failures
Remaining work for Step 16
- 16b: Extract
install_builtins()as top-level free function - 16c: Migrate
Vault::init(&Config)→Vault::init(&AppConfig) - 16d: Build
AppState::init(app_config, ...).await - 16e: Switch
main.rsand all 15Config::init()callers to the new flow - 16f: Delete Config runtime fields, bridge.rs,
Config::init, duplicated methods
Migration direction preserved
After 16a, no runtime behavior has changed. The new entry point exists but isn't wired in. The bridge flow continues as before:
YAML → Config::load_from_file
→ Config::init (unchanged, does all current mutations)
- load_envs, set_wrap, setup_document_loaders, ...
- setup_model, load_functions, load_mcp_servers
→ cfg.to_app_config() → AppConfig (via bridge)
→ cfg.to_request_context(AppState) → RequestContext
New entry point ready for 16e:
AppConfig::from_config(config) → AppConfig
(internally: to_app_config, load_envs, set_wrap,
setup_document_loaders, setup_user_agent, resolve_model)