@@ -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+ no_preflight : bool = False ,
219220) -> dict :
220221 """Log into Databricks, enforce AI Gateway v2, fetch model lists, persist state.
221222
@@ -229,44 +230,60 @@ 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 no_preflight is True, trust a prior ``ucode configure`` and skip the
234+ per-launch network round-trips entirely: no auth login, no token fetch, no
235+ AI Gateway probe, and no model discovery. The PAT/bearer is already exported
236+ (``apply_pat_environment`` in ``_launch_tool``) and the gateway was verified
237+ by that earlier configure, so a managed/headless re-launch needs none of it.
238+ The local profile/base-url derivation and state persistence still run.
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
239- if use_pat :
240- if not profile :
241- raise RuntimeError (
242- "--use-pat requires a Databricks CLI profile. Pass one via `--profiles <name>`."
243- )
244- pat = resolve_pat_token (profile )
245- if not pat :
246- raise RuntimeError (
247- f"--use-pat: profile '{ profile } ' has no personal access token in "
248- "~/.databrickscfg (its auth_type must be `pat`). Add a `token = <PAT>` "
249- f"entry under [{ profile } ], or re-run without --use-pat to use OAuth."
250- )
251- # Export the PAT for this process and launched agent subprocesses so
252- # every token fetch takes the static-bearer path. ensure_pat_bearer
253- # keeps a non-empty pre-set bearer (CI escape hatch) but treats an
254- # empty one as absent, so it never shadows the PAT. Pass the validated
255- # token to avoid re-reading ~/.databrickscfg.
256- ensure_pat_bearer (profile , pat )
257- ensure_databricks_auth (workspace , profile )
258- elif force_login :
259- run_databricks_login (workspace , profile )
246+ token : str | None = None
247+ if no_preflight :
248+ # A prior `ucode configure` already logged in and verified the AI Gateway,
249+ # and the PAT/bearer is already exported (apply_pat_environment), so skip
250+ # the auth login, token fetch, and gateway probe. Still resolve the
251+ # profile locally from ~/.databrickscfg so downstream CLI calls
252+ # disambiguate; no token is needed since model discovery is skipped too.
253+ if profile is None :
254+ profile = find_profile_name_for_host (workspace )
260255 else :
261- ensure_databricks_auth (workspace , profile )
262- # After login the profile exists in ~/.databrickscfg, so a host->profile
263- # lookup is reliable. Persist it so subsequent CLI calls disambiguate.
264- if profile is None :
265- profile = find_profile_name_for_host (workspace )
266- with spinner ("Verifying Unity AI Gateway..." ):
267- token = get_databricks_token (workspace , profile )
268- ensure_ai_gateway_v2 (workspace , token )
269- print_success ("Unity AI Gateway detected" )
256+ if use_pat :
257+ if not profile :
258+ raise RuntimeError (
259+ "--use-pat requires a Databricks CLI profile. Pass one via `--profiles <name>`."
260+ )
261+ pat = resolve_pat_token (profile )
262+ if not pat :
263+ raise RuntimeError (
264+ f"--use-pat: profile '{ profile } ' has no personal access token in "
265+ "~/.databrickscfg (its auth_type must be `pat`). Add a `token = <PAT>` "
266+ f"entry under [{ profile } ], or re-run without --use-pat to use OAuth."
267+ )
268+ # Export the PAT for this process and launched agent subprocesses so
269+ # every token fetch takes the static-bearer path. ensure_pat_bearer
270+ # keeps a non-empty pre-set bearer (CI escape hatch) but treats an
271+ # empty one as absent, so it never shadows the PAT. Pass the validated
272+ # token to avoid re-reading ~/.databrickscfg.
273+ ensure_pat_bearer (profile , pat )
274+ ensure_databricks_auth (workspace , profile )
275+ elif force_login :
276+ run_databricks_login (workspace , profile )
277+ else :
278+ ensure_databricks_auth (workspace , profile )
279+ # After login the profile exists in ~/.databrickscfg, so a host->profile
280+ # lookup is reliable. Persist it so subsequent CLI calls disambiguate.
281+ if profile is None :
282+ profile = find_profile_name_for_host (workspace )
283+ with spinner ("Verifying Unity AI Gateway..." ):
284+ token = get_databricks_token (workspace , profile )
285+ ensure_ai_gateway_v2 (workspace , token )
286+ print_success ("Unity AI Gateway detected" )
270287
271288 want_claude = (
272289 fetch_all or "claude" in tools or "opencode" in tools or "copilot" in tools or "pi" in tools
@@ -284,39 +301,45 @@ def configure_shared_state(
284301 codex_models = []
285302 oss_models = []
286303 web_search_model : str | None = None
287- if skip_model_discovery :
288- # Provider mode: the agent routes through a Model Provider Service and
289- # pins no Databricks model, so the full family discovery is unused. Web
290- # search (claude only) still needs one Responses-capable model, so fetch
291- # just that with a single call.
292- if want_claude :
293- with spinner ("Fetching web search model..." ):
294- ws_models , _ = discover_codex_models (workspace , token )
295- if ws_models :
296- web_search_model = ws_models [0 ]
297- 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.
302- with spinner ("Fetching available models..." ):
303- ms_claude , ms_codex , ms_gemini , ms_oss , ms_reason = discover_model_services (
304- workspace , token
305- )
304+ # Under no_preflight no discovery runs at all — the prior configure's
305+ # model lists are trusted (and preserved below, not overwritten).
306+ if not no_preflight :
307+ # token is always set by the auth+gateway block above when validating.
308+ assert token is not None
309+ if skip_model_discovery :
310+ # Provider mode: the agent routes through a Model Provider Service and
311+ # pins no Databricks model, so the full family discovery is unused. Web
312+ # search (claude only) still needs one Responses-capable model, so fetch
313+ # just that with a single call.
306314 if want_claude :
307- claude_models , claude_reason = ms_claude , ms_reason
308- if not claude_models :
309- claude_models , claude_reason = discover_claude_models (workspace , token )
310- if want_gemini :
311- gemini_models , gemini_reason = ms_gemini , ms_reason
312- if not gemini_models :
313- gemini_models , gemini_reason = discover_gemini_models (workspace , token )
314- if want_codex :
315- codex_models , codex_reason = ms_codex , ms_reason
316- if not codex_models :
317- codex_models , codex_reason = discover_codex_models (workspace , token )
318- if want_oss :
319- oss_models , oss_reason = ms_oss , ms_reason
315+ with spinner ("Fetching web search model..." ):
316+ ws_models , _ = discover_codex_models (workspace , token )
317+ if ws_models :
318+ web_search_model = ws_models [0 ]
319+ else :
320+ # UC-first, best-effort: one UC model-services call yields all families
321+ # as `system.ai.<model-name>` ids, bucketed by name. If a family comes
322+ # back empty (workspace without UC model-services, or the listing
323+ # failed), fall back to the per-family AI Gateway listing for that
324+ # family only.
325+ with spinner ("Fetching available models..." ):
326+ ms_claude , ms_codex , ms_gemini , ms_oss , ms_reason = discover_model_services (
327+ workspace , token
328+ )
329+ if want_claude :
330+ claude_models , claude_reason = ms_claude , ms_reason
331+ if not claude_models :
332+ claude_models , claude_reason = discover_claude_models (workspace , token )
333+ if want_gemini :
334+ gemini_models , gemini_reason = ms_gemini , ms_reason
335+ if not gemini_models :
336+ gemini_models , gemini_reason = discover_gemini_models (workspace , token )
337+ if want_codex :
338+ codex_models , codex_reason = ms_codex , ms_reason
339+ if not codex_models :
340+ codex_models , codex_reason = discover_codex_models (workspace , token )
341+ if want_oss :
342+ oss_models , oss_reason = ms_oss , ms_reason
320343 opencode_models : dict [str , list [str ]] = {}
321344 if claude_models :
322345 opencode_models ["anthropic" ] = list (claude_models .values ())
@@ -341,7 +364,11 @@ def configure_shared_state(
341364 else :
342365 state .pop ("use_pat" , None )
343366 state ["base_urls" ] = build_shared_base_urls (workspace )
344- if skip_model_discovery :
367+ if no_preflight :
368+ # No discovery ran; preserve the model lists the prior configure wrote
369+ # (they'd otherwise be clobbered with empties below).
370+ pass
371+ elif skip_model_discovery :
345372 # Don't clobber any previously-discovered Databricks model lists; provider
346373 # mode just doesn't refresh or use them. Persist the web-search model so
347374 # claude's web_search MCP keeps working through the normal gateway.
@@ -822,7 +849,12 @@ def _auto_configure_tool(tool: str) -> None:
822849 raise RuntimeError (f"{ spec ['display' ]} validation failed — config reverted." )
823850
824851
825- def _launch_tool (tool_name : str , ctx : typer .Context , provider : str | None = None ) -> None :
852+ def _launch_tool (
853+ tool_name : str ,
854+ ctx : typer .Context ,
855+ provider : str | None = None ,
856+ no_preflight : bool = False ,
857+ ) -> None :
826858 try :
827859 tool = normalize_tool (tool_name )
828860 existing = load_state ()
@@ -860,6 +892,7 @@ def _launch_tool(tool_name: str, ctx: typer.Context, provider: str | None = None
860892 profile = state .get ("profile" ),
861893 tools = [tool ],
862894 skip_model_discovery = bool (provider ),
895+ no_preflight = no_preflight ,
863896 )
864897 if provider :
865898 # Routing through a Model Provider Service pins no Databricks model;
@@ -892,6 +925,21 @@ def _launch_tool(tool_name: str, ctx: typer.Context, provider: str | None = None
892925 raise typer .Exit (130 ) from None
893926
894927
928+ # Launch-only escape hatch for managed/headless launchers (e.g. omnigent) that
929+ # have already run `ucode configure`: skip the ~5-10s per-launch auth + AI
930+ # Gateway re-validation. Distinct from the configure-only `--skip-validate`,
931+ # which skips the model smoke test.
932+ NoPreflightOption = Annotated [
933+ bool ,
934+ typer .Option (
935+ "--no-preflight" ,
936+ help = "Skip the per-launch Databricks auth + AI Gateway re-validation, trusting a "
937+ "prior `ucode configure` (used by managed/headless launchers where configure "
938+ "already ran)." ,
939+ ),
940+ ]
941+
942+
895943@app .command ("codex" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
896944def codex_cmd (
897945 ctx : typer .Context ,
@@ -904,9 +952,10 @@ def codex_cmd(
904952 "before any `--` separator." ,
905953 ),
906954 ] = None ,
955+ no_preflight : NoPreflightOption = False ,
907956) -> None :
908957 """Launch Codex via Databricks."""
909- _launch_tool ("codex" , ctx , provider = provider )
958+ _launch_tool ("codex" , ctx , provider = provider , no_preflight = no_preflight )
910959
911960
912961@app .command ("claude" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
@@ -921,35 +970,36 @@ def claude_cmd(
921970 "before any `--` separator." ,
922971 ),
923972 ] = None ,
973+ no_preflight : NoPreflightOption = False ,
924974) -> None :
925975 """Launch Claude Code via Databricks."""
926- _launch_tool ("claude" , ctx , provider = provider )
976+ _launch_tool ("claude" , ctx , provider = provider , no_preflight = no_preflight )
927977
928978
929979@app .command ("gemini" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
930- def gemini_cmd (ctx : typer .Context ) -> None :
980+ def gemini_cmd (ctx : typer .Context , no_preflight : NoPreflightOption = False ) -> None :
931981 """Launch Gemini CLI via Databricks."""
932- _launch_tool ("gemini" , ctx )
982+ _launch_tool ("gemini" , ctx , no_preflight = no_preflight )
933983
934984
935985@app .command (
936986 "opencode" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True }
937987)
938- def opencode_cmd (ctx : typer .Context ) -> None :
988+ def opencode_cmd (ctx : typer .Context , no_preflight : NoPreflightOption = False ) -> None :
939989 """Launch OpenCode via Databricks."""
940- _launch_tool ("opencode" , ctx )
990+ _launch_tool ("opencode" , ctx , no_preflight = no_preflight )
941991
942992
943993@app .command ("copilot" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
944- def copilot_cmd (ctx : typer .Context ) -> None :
994+ def copilot_cmd (ctx : typer .Context , no_preflight : NoPreflightOption = False ) -> None :
945995 """Launch GitHub Copilot CLI via Databricks."""
946- _launch_tool ("copilot" , ctx )
996+ _launch_tool ("copilot" , ctx , no_preflight = no_preflight )
947997
948998
949999@app .command ("pi" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
950- def pi_cmd (ctx : typer .Context ) -> None :
1000+ def pi_cmd (ctx : typer .Context , no_preflight : NoPreflightOption = False ) -> None :
9511001 """Launch Pi coding agent via Databricks."""
952- _launch_tool ("pi" , ctx )
1002+ _launch_tool ("pi" , ctx , no_preflight = no_preflight )
9531003
9541004
9551005@configure_app .callback (invoke_without_command = True )
0 commit comments