55import json
66
77from ucode .config_io import APP_DIR , is_dry_run
8- from ucode .databricks import build_shared_base_urls
8+ from ucode .databricks import build_auth_shell_command , build_shared_base_urls
99
1010STATE_PATH = APP_DIR / "state.json"
1111STATE_VERSION = 3
12+ AUTH_COMMAND_TIMEOUT_MS = 5000
13+ AUTH_REFRESH_INTERVAL_MS = 900_000
1214
1315
1416def load_full_state () -> dict :
@@ -52,6 +54,12 @@ def save_state(state: dict) -> None:
5254
5355
5456def hydrate_state (state : dict ) -> dict :
57+ """Normalize a workspace state entry and add derived harness config.
58+
59+ :param state: Raw workspace state entry from ``state.json``.
60+ :returns: Hydrated workspace state with stable ``managed_configs``,
61+ ``base_urls``, and per-agent ``agents`` entries.
62+ """
5563 if not isinstance (state , dict ):
5664 return {}
5765
@@ -71,12 +79,81 @@ def hydrate_state(state: dict) -> dict:
7179 workspace = hydrated .get ("workspace" )
7280 if workspace :
7381 hydrated ["base_urls" ] = build_shared_base_urls (workspace )
82+ hydrated ["agents" ] = build_agent_state (hydrated )
7483 else :
7584 hydrated ["base_urls" ] = {}
85+ hydrated ["agents" ] = {}
7686
7787 return hydrated
7888
7989
90+ def build_agent_state (state : dict ) -> dict [str , dict ]:
91+ """Build per-agent harness configuration for a workspace.
92+
93+ The returned shape is intended for downstream tools that want to reuse
94+ ucode's configured gateway URLs and auth command without duplicating
95+ endpoint construction logic.
96+
97+ :param state: Hydrated workspace state containing ``workspace``,
98+ ``base_urls``, and discovered model lists.
99+ :returns: Mapping from agent name to its reusable configuration.
100+ """
101+ workspace = state .get ("workspace" )
102+ if not isinstance (workspace , str ) or not workspace :
103+ return {}
104+
105+ base_urls_value = state .get ("base_urls" )
106+ base_urls = base_urls_value if isinstance (base_urls_value , dict ) else {}
107+ auth_command = build_auth_shell_command (workspace )
108+ claude_models_value = state .get ("claude_models" )
109+ claude_models : dict = claude_models_value if isinstance (claude_models_value , dict ) else {}
110+ codex_models_value = state .get ("codex_models" )
111+ codex_models = codex_models_value if isinstance (codex_models_value , list ) else []
112+ gemini_models_value = state .get ("gemini_models" )
113+ gemini_models = gemini_models_value if isinstance (gemini_models_value , list ) else []
114+
115+ claude_model = (
116+ claude_models .get ("opus" ) or claude_models .get ("sonnet" ) or claude_models .get ("haiku" )
117+ )
118+ codex_model = codex_models [0 ] if codex_models else None
119+ pi_model = claude_model or codex_model or (gemini_models [0 ] if gemini_models else None )
120+
121+ agents : dict [str , dict ] = {
122+ "claude" : {
123+ "model" : claude_model ,
124+ "base_url" : base_urls .get ("claude" ),
125+ "auth_command" : auth_command ,
126+ "auth_refresh_interval_ms" : AUTH_REFRESH_INTERVAL_MS ,
127+ "env" : {
128+ "ANTHROPIC_BASE_URL" : base_urls .get ("claude" ),
129+ "CLAUDE_CODE_API_KEY_HELPER_TTL_MS" : str (AUTH_REFRESH_INTERVAL_MS ),
130+ "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS" : "1" ,
131+ },
132+ },
133+ "codex" : {
134+ "model" : codex_model ,
135+ "base_url" : base_urls .get ("codex" ),
136+ "auth_command" : auth_command ,
137+ "auth" : {
138+ "command" : "sh" ,
139+ "args" : ["-c" , auth_command ],
140+ "timeout_ms" : AUTH_COMMAND_TIMEOUT_MS ,
141+ "refresh_interval_ms" : AUTH_REFRESH_INTERVAL_MS ,
142+ },
143+ },
144+ "pi" : {
145+ "model" : pi_model ,
146+ "base_urls" : base_urls .get ("pi" ) if isinstance (base_urls .get ("pi" ), dict ) else {},
147+ "auth_command" : auth_command ,
148+ "auth_refresh_interval_ms" : AUTH_REFRESH_INTERVAL_MS ,
149+ },
150+ }
151+ return {
152+ name : {key : value for key , value in config .items () if value is not None }
153+ for name , config in agents .items ()
154+ }
155+
156+
80157def clear_state () -> None :
81158 """Remove the current workspace entry from state."""
82159 full = load_full_state ()
0 commit comments