@@ -216,6 +216,7 @@ def configure_shared_state(
216216 force_login : bool = False ,
217217 use_pat : bool | None = None ,
218218 skip_model_discovery : bool = False ,
219+ skip_preflight : bool = False ,
219220) -> dict :
220221 """Log into Databricks, enforce AI Gateway v2, fetch model lists, persist state.
221222
@@ -229,13 +230,59 @@ def configure_shared_state(
229230 ``--profile`` to every CLI invocation so ambiguous `~/.databrickscfg`
230231 entries (e.g. DEFAULT and a named profile both pointing at the same host)
231232 don't error out. If ``None``, we resolve it from the host after login.
233+ If skip_preflight is True, skip the entire preflight block below — auth
234+ validation, the AI Gateway probe, and model discovery — trusting a prior
235+ ``ucode configure``. The PAT/bearer is already exported (``apply_pat_environment``
236+ in ``_launch_tool``) and the gateway was verified by that earlier configure.
237+ Only the local profile resolution and the shared state assembly still run;
238+ the saved model lists are preserved.
232239 """
233240 workspace = normalize_workspace_url (workspace )
234241 prior_state = load_state ()
235242 previous_workspace = prior_state .get ("workspace" )
236243 if use_pat is None :
237244 use_pat = bool (prior_state .get ("use_pat" )) and previous_workspace == workspace
238245 fetch_all = tools is None
246+
247+ # Resolve the profile locally from ~/.databrickscfg. On a first-time OAuth
248+ # login the matching profile may not exist yet; the preflight below
249+ # re-resolves it once `databricks auth login` has created it.
250+ if profile is None :
251+ profile = find_profile_name_for_host (workspace )
252+
253+ # Assemble the shared workspace state that doesn't depend on model discovery:
254+ # workspace, profile, auth mode, base URLs. --skip-preflight persists exactly
255+ # this and returns, trusting a prior `ucode configure` — it already validated
256+ # auth + the AI Gateway and saved the model lists, which load_state carried
257+ # over above and we leave untouched.
258+ state = load_state ()
259+ state ["workspace" ] = workspace
260+ if profile :
261+ state ["profile" ] = profile
262+ else :
263+ state .pop ("profile" , None )
264+ # UC discovery is now always-on; drop any flag persisted by older versions.
265+ state .pop ("uc_enabled" , None )
266+ # Persist the auth mode so launches rebuild the same (PAT-based) agent
267+ # auth command; an explicit re-configure without --use-pat clears it.
268+ if use_pat :
269+ state ["use_pat" ] = True
270+ else :
271+ state .pop ("use_pat" , None )
272+ state ["base_urls" ] = build_shared_base_urls (workspace )
273+
274+ if skip_preflight :
275+ save_state (state )
276+ # Scrub MCP entries ucode wrote for a previous workspace.
277+ if previous_workspace and previous_workspace != workspace :
278+ purge_cross_workspace_mcp_residue (state , workspace )
279+ # Diagnostic reasons are transient (attached after save_state so they
280+ # don't land on disk). No discovery ran, so there is nothing to report.
281+ state ["_discovery_reasons" ] = {"claude" : None , "gemini" : None , "codex" : None , "oss" : None }
282+ return state
283+
284+ # ── Preflight (bypassed above under --skip-preflight): validate Databricks
285+ # auth + the AI Gateway, then discover the available models. ──
239286 if use_pat :
240287 if not profile :
241288 raise RuntimeError (
@@ -260,9 +307,11 @@ def configure_shared_state(
260307 else :
261308 ensure_databricks_auth (workspace , profile )
262309 # After login the profile exists in ~/.databrickscfg, so a host->profile
263- # lookup is reliable. Persist it so subsequent CLI calls disambiguate .
310+ # lookup is reliable even when it returned nothing above .
264311 if profile is None :
265312 profile = find_profile_name_for_host (workspace )
313+ if profile :
314+ state ["profile" ] = profile
266315 with spinner ("Verifying Unity AI Gateway..." ):
267316 token = get_databricks_token (workspace , profile )
268317 ensure_ai_gateway_v2 (workspace , token )
@@ -283,6 +332,7 @@ def configure_shared_state(
283332 gemini_models = []
284333 codex_models = []
285334 oss_models = []
335+ opencode_models : dict [str , list [str ]] = {}
286336 web_search_model : str | None = None
287337 if skip_model_discovery :
288338 # Provider mode: the agent routes through a Model Provider Service and
@@ -295,10 +345,11 @@ def configure_shared_state(
295345 if ws_models :
296346 web_search_model = ws_models [0 ]
297347 else :
298- # UC-first, best-effort: one UC model-services call yields all families as
299- # `system.ai.<model-name>` ids, bucketed by name. If a family comes back
300- # empty (workspace without UC model-services, or the listing failed), fall
301- # back to the per-family AI Gateway listing for that family only.
348+ # UC-first, best-effort: one UC model-services call yields all families
349+ # as `system.ai.<model-name>` ids, bucketed by name. If a family comes
350+ # back empty (workspace without UC model-services, or the listing
351+ # failed), fall back to the per-family AI Gateway listing for that
352+ # family only.
302353 with spinner ("Fetching available models..." ):
303354 ms_claude , ms_codex , ms_gemini , ms_oss , ms_reason = discover_model_services (
304355 workspace , token
@@ -317,30 +368,13 @@ def configure_shared_state(
317368 codex_models , codex_reason = discover_codex_models (workspace , token )
318369 if want_oss :
319370 oss_models , oss_reason = ms_oss , ms_reason
320- opencode_models : dict [str , list [str ]] = {}
321- if claude_models :
322- opencode_models ["anthropic" ] = list (claude_models .values ())
323- if gemini_models :
324- opencode_models ["gemini" ] = gemini_models
325- if oss_models :
326- opencode_models ["oss" ] = oss_models
327-
328- # Merge into existing workspace state so prior tool configs are preserved.
329- state = load_state ()
330- state ["workspace" ] = workspace
331- if profile :
332- state ["profile" ] = profile
333- else :
334- state .pop ("profile" , None )
335- # UC discovery is now always-on; drop any flag persisted by older versions.
336- state .pop ("uc_enabled" , None )
337- # Persist the auth mode so launches rebuild the same (PAT-based) agent
338- # auth command; an explicit re-configure without --use-pat clears it.
339- if use_pat :
340- state ["use_pat" ] = True
341- else :
342- state .pop ("use_pat" , None )
343- state ["base_urls" ] = build_shared_base_urls (workspace )
371+ if claude_models :
372+ opencode_models ["anthropic" ] = list (claude_models .values ())
373+ if gemini_models :
374+ opencode_models ["gemini" ] = gemini_models
375+ if oss_models :
376+ opencode_models ["oss" ] = oss_models
377+
344378 if skip_model_discovery :
345379 # Don't clobber any previously-discovered Databricks model lists; provider
346380 # mode just doesn't refresh or use them. Persist the web-search model so
@@ -822,7 +856,12 @@ def _auto_configure_tool(tool: str) -> None:
822856 raise RuntimeError (f"{ spec ['display' ]} validation failed — config reverted." )
823857
824858
825- def _launch_tool (tool_name : str , ctx : typer .Context , provider : str | None = None ) -> None :
859+ def _launch_tool (
860+ tool_name : str ,
861+ ctx : typer .Context ,
862+ provider : str | None = None ,
863+ skip_preflight : bool = False ,
864+ ) -> None :
826865 try :
827866 tool = normalize_tool (tool_name )
828867 existing = load_state ()
@@ -860,6 +899,7 @@ def _launch_tool(tool_name: str, ctx: typer.Context, provider: str | None = None
860899 profile = state .get ("profile" ),
861900 tools = [tool ],
862901 skip_model_discovery = bool (provider ),
902+ skip_preflight = skip_preflight ,
863903 )
864904 if provider :
865905 # Routing through a Model Provider Service pins no Databricks model;
@@ -892,6 +932,20 @@ def _launch_tool(tool_name: str, ctx: typer.Context, provider: str | None = None
892932 raise typer .Exit (130 ) from None
893933
894934
935+ # Launch-only escape hatch for managed/headless launchers (e.g. omnigent) that
936+ # have already run `ucode configure`: skip the ~5-10s per-launch auth + AI
937+ # Gateway re-validation. Distinct from the configure-only `--skip-validate`,
938+ # which skips the model smoke test.
939+ SkipPreflightOption = Annotated [
940+ bool ,
941+ typer .Option (
942+ "--skip-preflight" ,
943+ help = "Skip the per-launch Databricks auth + AI Gateway re-validation, trusting a "
944+ "prior `ucode configure`." ,
945+ ),
946+ ]
947+
948+
895949@app .command ("codex" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
896950def codex_cmd (
897951 ctx : typer .Context ,
@@ -904,9 +958,10 @@ def codex_cmd(
904958 "before any `--` separator." ,
905959 ),
906960 ] = None ,
961+ skip_preflight : SkipPreflightOption = False ,
907962) -> None :
908963 """Launch Codex via Databricks."""
909- _launch_tool ("codex" , ctx , provider = provider )
964+ _launch_tool ("codex" , ctx , provider = provider , skip_preflight = skip_preflight )
910965
911966
912967@app .command ("claude" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
@@ -921,35 +976,36 @@ def claude_cmd(
921976 "before any `--` separator." ,
922977 ),
923978 ] = None ,
979+ skip_preflight : SkipPreflightOption = False ,
924980) -> None :
925981 """Launch Claude Code via Databricks."""
926- _launch_tool ("claude" , ctx , provider = provider )
982+ _launch_tool ("claude" , ctx , provider = provider , skip_preflight = skip_preflight )
927983
928984
929985@app .command ("gemini" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
930- def gemini_cmd (ctx : typer .Context ) -> None :
986+ def gemini_cmd (ctx : typer .Context , skip_preflight : SkipPreflightOption = False ) -> None :
931987 """Launch Gemini CLI via Databricks."""
932- _launch_tool ("gemini" , ctx )
988+ _launch_tool ("gemini" , ctx , skip_preflight = skip_preflight )
933989
934990
935991@app .command (
936992 "opencode" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True }
937993)
938- def opencode_cmd (ctx : typer .Context ) -> None :
994+ def opencode_cmd (ctx : typer .Context , skip_preflight : SkipPreflightOption = False ) -> None :
939995 """Launch OpenCode via Databricks."""
940- _launch_tool ("opencode" , ctx )
996+ _launch_tool ("opencode" , ctx , skip_preflight = skip_preflight )
941997
942998
943999@app .command ("copilot" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
944- def copilot_cmd (ctx : typer .Context ) -> None :
1000+ def copilot_cmd (ctx : typer .Context , skip_preflight : SkipPreflightOption = False ) -> None :
9451001 """Launch GitHub Copilot CLI via Databricks."""
946- _launch_tool ("copilot" , ctx )
1002+ _launch_tool ("copilot" , ctx , skip_preflight = skip_preflight )
9471003
9481004
9491005@app .command ("pi" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
950- def pi_cmd (ctx : typer .Context ) -> None :
1006+ def pi_cmd (ctx : typer .Context , skip_preflight : SkipPreflightOption = False ) -> None :
9511007 """Launch Pi coding agent via Databricks."""
952- _launch_tool ("pi" , ctx )
1008+ _launch_tool ("pi" , ctx , skip_preflight = skip_preflight )
9531009
9541010
9551011@configure_app .callback (invoke_without_command = True )
0 commit comments