Skip to content

Commit 2aadc0a

Browse files
authored
fix: resolve global config circular references issue (#29)
2 parents 0377053 + b751684 commit 2aadc0a

File tree

8 files changed

+228
-111
lines changed

8 files changed

+228
-111
lines changed

agentkit/client/base_agentkit_client.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
from typing import Any, Dict, Union, Optional
2121

2222
from agentkit.client.base_service_client import BaseServiceClient, ApiConfig
23-
from agentkit.utils.ve_sign import get_volc_agentkit_host_info
24-
from agentkit.toolkit.config.global_config import get_global_config
23+
from agentkit.utils.ve_sign import get_volc_agentkit_host_info, get_volc_agentkit_scheme
2524

2625

2726
class BaseAgentkitClient(BaseServiceClient):
@@ -78,13 +77,11 @@ def _get_service_config(self) -> Dict[str, str]:
7877
Dictionary with host, api_version, and service
7978
"""
8079
host, api_version, service = get_volc_agentkit_host_info()
81-
gc = get_global_config()
82-
scheme = gc.agentkit_schema or "https"
8380
return {
84-
"host": gc.agentkit_host or host,
81+
"host": host,
8582
"api_version": api_version,
8683
"service": service,
87-
"scheme": scheme,
84+
"scheme": get_volc_agentkit_scheme(),
8885
}
8986

9087
def _get(self, api_action: str, params: Dict[str, Any] = None) -> str:

agentkit/client/base_iam_client.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from typing import Dict, Union
2121

2222
from agentkit.client.base_service_client import BaseServiceClient, ApiConfig
23-
from agentkit.toolkit.config.global_config import get_global_config
23+
from agentkit.utils.ve_sign import get_volc_iam_host_scheme
2424

2525

2626
class BaseIAMClient(BaseServiceClient):
@@ -79,11 +79,9 @@ def _get_service_config(self) -> Dict[str, str]:
7979
Returns:
8080
Dictionary with host, api_version, and service
8181
"""
82-
gc = get_global_config()
83-
scheme = gc.iam_schema or "https"
84-
host = gc.iam_host or self.IAM_HOST
82+
host, scheme = get_volc_iam_host_scheme()
8583
return {
86-
"host": host,
84+
"host": host or self.IAM_HOST,
8785
"api_version": self.IAM_API_VERSION,
8886
"service": self.IAM_SERVICE_CODE,
8987
"scheme": scheme,

agentkit/toolkit/cli/cli_launch.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,66 @@
2626
def launch_command(
2727
config_file: Path = typer.Option("agentkit.yaml", help="Configuration file"),
2828
platform: str = typer.Option("auto", help="Build platform"),
29+
preflight_mode: str = typer.Option(
30+
"",
31+
"--preflight-mode",
32+
help="Preflight behavior: prompt|fail|warn|skip",
33+
),
2934
):
3035
"""Build and deploy in one command."""
3136
from agentkit.toolkit.executors import LifecycleExecutor
3237
from agentkit.toolkit.cli.console_reporter import ConsoleReporter
3338
from agentkit.toolkit.context import ExecutionContext
39+
from agentkit.toolkit.models import PreflightMode
40+
from agentkit.toolkit.config.global_config import get_global_config
3441

3542
console.print("[green]Launching agent...[/green]")
3643

3744
# Set execution context - CLI uses ConsoleReporter (with colored output and progress)
3845
reporter = ConsoleReporter()
3946
ExecutionContext.set_reporter(reporter)
4047

48+
resolved_mode = PreflightMode.PROMPT
49+
mode_map = {
50+
"prompt": PreflightMode.PROMPT,
51+
"fail": PreflightMode.FAIL,
52+
"warn": PreflightMode.WARN,
53+
"skip": PreflightMode.SKIP,
54+
}
55+
56+
cli_mode = preflight_mode.strip().lower()
57+
if cli_mode:
58+
if cli_mode not in mode_map:
59+
console.print(
60+
"[red]Invalid --preflight-mode. Allowed: prompt|fail|warn|skip[/red]"
61+
)
62+
raise typer.Exit(2)
63+
resolved_mode = mode_map[cli_mode]
64+
else:
65+
try:
66+
gm = (
67+
(
68+
getattr(
69+
getattr(get_global_config(), "defaults", None),
70+
"preflight_mode",
71+
None,
72+
)
73+
or ""
74+
)
75+
.strip()
76+
.lower()
77+
)
78+
if gm in mode_map:
79+
resolved_mode = mode_map[gm]
80+
except Exception:
81+
pass
82+
4183
executor = LifecycleExecutor(reporter=reporter)
42-
result = executor.launch(config_file=str(config_file), platform=platform)
84+
result = executor.launch(
85+
config_file=str(config_file),
86+
platform=platform,
87+
preflight_mode=resolved_mode,
88+
)
4389

4490
# Format output
4591
if result.success:

agentkit/toolkit/config/global_config.py

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
from typing import Optional
2727
import logging
2828

29+
from agentkit.utils.global_config_io import (
30+
read_global_config_dict,
31+
write_global_config_dict,
32+
)
33+
2934
from .utils import is_valid_config
3035

3136
logger = logging.getLogger(__name__)
@@ -228,20 +233,18 @@ def load(self) -> GlobalConfig:
228233
Returns:
229234
GlobalConfig instance
230235
"""
231-
if not self.config_path.exists():
232-
logger.debug(f"Global config file does not exist: {self.config_path}")
236+
data = read_global_config_dict(self.config_path)
237+
if not data:
238+
logger.debug(
239+
f"Global config file does not exist or empty: {self.config_path}"
240+
)
233241
return GlobalConfig()
234-
235242
try:
236-
import yaml
237-
238-
with open(self.config_path, "r", encoding="utf-8") as f:
239-
data = yaml.safe_load(f) or {}
240243
logger.debug(f"Loaded global config from: {self.config_path}")
241244
return GlobalConfig.from_dict(data)
242245
except Exception as e:
243246
logger.debug(
244-
f"Failed to load global config, using empty config: {e}", exc_info=True
247+
f"Failed to parse global config, using empty config: {e}", exc_info=True
245248
)
246249
return GlobalConfig()
247250

@@ -251,27 +254,7 @@ def save(self, config: GlobalConfig):
251254
Args:
252255
config: Configuration object to persist
253256
"""
254-
# Ensure parent directory exists
255-
self.config_path.parent.mkdir(parents=True, exist_ok=True)
256-
257-
# Write YAML config
258-
with open(self.config_path, "w", encoding="utf-8") as f:
259-
import yaml
260-
261-
yaml.dump(
262-
config.to_dict(),
263-
f,
264-
default_flow_style=False,
265-
allow_unicode=True,
266-
sort_keys=False,
267-
)
268-
269-
# Restrict file permission to owner read/write only
270-
try:
271-
self.config_path.chmod(0o600)
272-
except Exception as e:
273-
logger.warning(f"Failed to set config file permission: {e}")
274-
257+
write_global_config_dict(config.to_dict(), self.config_path)
275258
logger.info(f"Global config saved: {self.config_path}")
276259

277260
def get_config(self, force_reload: bool = False) -> GlobalConfig:

agentkit/toolkit/executors/lifecycle_executor.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -144,23 +144,6 @@ def launch(
144144

145145
# Preflight check: verify required cloud services for both build and deploy
146146
# We do this once at the start for better UX (single prompt for all missing services)
147-
# Override preflight_mode from global config defaults if configured
148-
try:
149-
from agentkit.toolkit.config.global_config import get_global_config
150-
151-
gc = get_global_config()
152-
gm = getattr(getattr(gc, "defaults", None), "preflight_mode", None)
153-
if gm:
154-
gm_map = {
155-
"prompt": PreflightMode.PROMPT,
156-
"fail": PreflightMode.FAIL,
157-
"warn": PreflightMode.WARN,
158-
"skip": PreflightMode.SKIP,
159-
}
160-
preflight_mode = gm_map.get(gm.lower(), preflight_mode)
161-
except Exception:
162-
pass
163-
164147
if preflight_mode != PreflightMode.SKIP:
165148
# Load config first to get launch_type
166149
config = self._load_config(config_dict, config_file)

agentkit/toolkit/runners/ve_agentkit.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -584,34 +584,6 @@ def _build_authorizer_config_for_create(
584584
)
585585
)
586586

587-
def _build_authorizer_config_for_update(
588-
self, config: VeAgentkitRunnerConfig
589-
) -> runtime_types.AuthorizerForUpdateRuntime:
590-
"""Build authorizer configuration for updating an existing Runtime.
591-
592-
Args:
593-
config: Runner configuration.
594-
595-
Returns:
596-
AuthorizerForUpdateRuntime: Authorizer configuration for update request.
597-
"""
598-
if config.runtime_auth_type == AUTH_TYPE_CUSTOM_JWT:
599-
return runtime_types.AuthorizerForUpdateRuntime(
600-
custom_jwt_authorizer=runtime_types.AuthorizerCustomJwtAuthorizerForUpdateRuntime(
601-
discovery_url=config.runtime_jwt_discovery_url,
602-
allowed_clients=config.runtime_jwt_allowed_clients
603-
if config.runtime_jwt_allowed_clients
604-
else None,
605-
)
606-
)
607-
else:
608-
return runtime_types.AuthorizerForUpdateRuntime(
609-
key_auth=runtime_types.AuthorizerKeyAuthForUpdateRuntime(
610-
api_key_name=config.runtime_apikey_name,
611-
api_key_location=API_KEY_LOCATION,
612-
)
613-
)
614-
615587
def _create_new_runtime(self, config: VeAgentkitRunnerConfig) -> DeployResult:
616588
"""Create a new Runtime instance.
617589

agentkit/utils/global_config_io.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from typing import Any, Optional, Tuple
5+
6+
7+
_cache: Tuple[Optional[float], dict] = (None, {})
8+
9+
10+
def get_default_global_config_path() -> Path:
11+
return Path.home() / ".agentkit" / "config.yaml"
12+
13+
14+
def read_global_config_dict(
15+
config_path: Optional[Path] = None,
16+
*,
17+
force_reload: bool = False,
18+
) -> dict:
19+
global _cache
20+
21+
path = config_path or get_default_global_config_path()
22+
23+
try:
24+
mtime = path.stat().st_mtime
25+
except FileNotFoundError:
26+
if force_reload:
27+
_cache = (None, {})
28+
return {}
29+
except Exception:
30+
return {}
31+
32+
cached_mtime, cached_data = _cache
33+
if not force_reload and cached_mtime == mtime:
34+
return cached_data
35+
36+
try:
37+
import yaml
38+
39+
with open(path, "r", encoding="utf-8") as f:
40+
data = yaml.safe_load(f) or {}
41+
parsed = data if isinstance(data, dict) else {}
42+
_cache = (mtime, parsed)
43+
return parsed
44+
except Exception:
45+
return {}
46+
47+
48+
def write_global_config_dict(
49+
data: dict,
50+
config_path: Optional[Path] = None,
51+
) -> None:
52+
global _cache
53+
54+
path = config_path or get_default_global_config_path()
55+
path.parent.mkdir(parents=True, exist_ok=True)
56+
57+
import yaml
58+
59+
with open(path, "w", encoding="utf-8") as f:
60+
yaml.dump(
61+
data,
62+
f,
63+
default_flow_style=False,
64+
allow_unicode=True,
65+
sort_keys=False,
66+
)
67+
68+
try:
69+
path.chmod(0o600)
70+
except Exception:
71+
pass
72+
73+
try:
74+
mtime = path.stat().st_mtime
75+
_cache = (mtime, data)
76+
except Exception:
77+
pass
78+
79+
80+
def get_path_value(data: Any, *keys: str) -> Any:
81+
cur = data
82+
for key in keys:
83+
if not isinstance(cur, dict):
84+
return None
85+
cur = cur.get(key)
86+
return cur
87+
88+
89+
def get_global_config_str(
90+
*keys: str,
91+
fallback_keys: Optional[Tuple[str, ...]] = None,
92+
config_path: Optional[Path] = None,
93+
) -> str:
94+
data = read_global_config_dict(config_path)
95+
v = get_path_value(data, *keys)
96+
if isinstance(v, str) and v:
97+
return v
98+
if fallback_keys:
99+
v2 = get_path_value(data, *fallback_keys)
100+
if isinstance(v2, str) and v2:
101+
return v2
102+
return ""
103+
104+
105+
def get_global_config_bool(
106+
*keys: str,
107+
fallback_keys: Optional[Tuple[str, ...]] = None,
108+
config_path: Optional[Path] = None,
109+
):
110+
data = read_global_config_dict(config_path)
111+
v = get_path_value(data, *keys)
112+
if isinstance(v, bool):
113+
return v
114+
if fallback_keys:
115+
v2 = get_path_value(data, *fallback_keys)
116+
if isinstance(v2, bool):
117+
return v2
118+
return None

0 commit comments

Comments
 (0)