@@ -50,6 +50,8 @@ class Settings:
5050 codex_cli_command : str = "codex.cmd"
5151 codex_cli_model : str | None = None
5252 model_candidates : tuple [str , ...] = DEFAULT_MODEL_CANDIDATES
53+ ollama_base_url : str | None = None
54+ ollama_model : str = "gemma3"
5355
5456 def with_run_scope (
5557 self ,
@@ -110,14 +112,22 @@ def _split_candidates(raw_value: str | None) -> tuple[str, ...]:
110112 return values or DEFAULT_MODEL_CANDIDATES
111113
112114
113- def _default_fallback_chain (* , anthropic_model : str | None , anthropic_api_key : str | None ) -> tuple [str , ...]:
115+ def _default_fallback_chain (
116+ * ,
117+ anthropic_model : str | None ,
118+ anthropic_api_key : str | None ,
119+ ollama_base_url : str | None = None ,
120+ ollama_model : str = "gemma3" ,
121+ ) -> tuple [str , ...]:
114122 chain : list [str ] = [
115123 "gemini:gemini-2.5-pro" ,
116124 "gemini:gemini-2.5-flash" ,
117125 "gemini:gemini-2.5-flash-lite" ,
118126 ]
119127 if anthropic_api_key :
120128 chain .insert (1 , f"anthropic:{ anthropic_model or DEFAULT_ANTHROPIC_MODEL } " )
129+ if ollama_base_url :
130+ chain .insert (0 , f"ollama:{ ollama_model } " )
121131 chain .extend (
122132 [
123133 "claude-cli" ,
@@ -133,11 +143,15 @@ def _split_fallback_chain(
133143 * ,
134144 anthropic_model : str | None ,
135145 anthropic_api_key : str | None ,
146+ ollama_base_url : str | None = None ,
147+ ollama_model : str = "gemma3" ,
136148) -> tuple [str , ...]:
137149 if not raw_value :
138150 return _default_fallback_chain (
139151 anthropic_model = anthropic_model ,
140152 anthropic_api_key = anthropic_api_key ,
153+ ollama_base_url = ollama_base_url ,
154+ ollama_model = ollama_model ,
141155 )
142156 values = tuple (part .strip () for part in raw_value .split ("," ) if part .strip ())
143157 return values
@@ -185,8 +199,13 @@ def load_settings(*, env_path: Path | None = None, project_root: Path | None = N
185199 load_dotenv (dotenv_path = dotenv_path , override = False )
186200
187201 api_key = (os .getenv ("MAF_API_KEY" ) or os .getenv ("GEMINI_API_KEY" ) or "" ).strip ()
188- if not api_key :
189- raise ValueError ("Set GEMINI_API_KEY or MAF_API_KEY in the repo .env file." )
202+ ollama_base_url = (os .getenv ("OLLAMA_BASE_URL" ) or "" ).strip () or None
203+ ollama_model = (os .getenv ("OLLAMA_MODEL" ) or "gemma3" ).strip ()
204+ if not api_key and not ollama_base_url :
205+ raise ValueError (
206+ "Set GEMINI_API_KEY or MAF_API_KEY in the repo .env file, "
207+ "or set OLLAMA_BASE_URL for local-only operation."
208+ )
190209
191210 model = (os .getenv ("MAF_MODEL" ) or os .getenv ("GEMINI_MODEL" ) or DEFAULT_MODEL ).strip ()
192211 base_url = (os .getenv ("MAF_BASE_URL" ) or os .getenv ("GEMINI_BASE_URL" ) or DEFAULT_GEMINI_BASE_URL ).strip ()
@@ -215,7 +234,11 @@ def load_settings(*, env_path: Path | None = None, project_root: Path | None = N
215234 os .getenv ("MAF_FALLBACK_CHAIN" ),
216235 anthropic_model = anthropic_model ,
217236 anthropic_api_key = anthropic_api_key ,
237+ ollama_base_url = ollama_base_url ,
238+ ollama_model = ollama_model ,
218239 ),
240+ ollama_base_url = ollama_base_url ,
241+ ollama_model = ollama_model ,
219242 anthropic_api_key = anthropic_api_key ,
220243 anthropic_model = anthropic_model or DEFAULT_ANTHROPIC_MODEL ,
221244 gemini_cli_command = (os .getenv ("GEMINI_CLI_COMMAND" ) or "gemini.cmd" ).strip (),
0 commit comments