synth-ai 0.2.14__py3-none-any.whl → 0.2.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synth-ai might be problematic. Click here for more details.
- examples/README.md +1 -0
- examples/multi_step/SFT_README.md +147 -0
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +9 -9
- examples/multi_step/configs/crafter_sft_qwen30b_lora.toml +62 -0
- examples/multi_step/convert_traces_to_sft.py +84 -0
- examples/multi_step/run_sft_qwen30b.sh +45 -0
- examples/qwen_coder/configs/coder_lora_30b.toml +2 -1
- examples/qwen_coder/configs/coder_lora_4b.toml +2 -1
- examples/qwen_coder/configs/coder_lora_small.toml +2 -1
- examples/qwen_vl/BUGS_AND_FIXES.md +232 -0
- examples/qwen_vl/IMAGE_VALIDATION_COMPLETE.md +271 -0
- examples/qwen_vl/IMAGE_VALIDATION_SUMMARY.md +260 -0
- examples/qwen_vl/INFERENCE_SFT_TESTS.md +412 -0
- examples/qwen_vl/NEXT_STEPS_2B.md +325 -0
- examples/qwen_vl/QUICKSTART.md +327 -0
- examples/qwen_vl/QUICKSTART_RL_VISION.md +110 -0
- examples/qwen_vl/README.md +154 -0
- examples/qwen_vl/RL_VISION_COMPLETE.md +475 -0
- examples/qwen_vl/RL_VISION_TESTING.md +333 -0
- examples/qwen_vl/SDK_VISION_INTEGRATION.md +328 -0
- examples/qwen_vl/SETUP_COMPLETE.md +275 -0
- examples/qwen_vl/VISION_TESTS_COMPLETE.md +490 -0
- examples/qwen_vl/VLM_PIPELINE_COMPLETE.md +242 -0
- examples/qwen_vl/__init__.py +2 -0
- examples/qwen_vl/collect_data_via_cli.md +423 -0
- examples/qwen_vl/collect_vision_traces.py +368 -0
- examples/qwen_vl/configs/crafter_rl_vision_qwen3vl4b.toml +127 -0
- examples/qwen_vl/configs/crafter_vlm_sft_example.toml +60 -0
- examples/qwen_vl/configs/eval_gpt4o_mini_vision.toml +43 -0
- examples/qwen_vl/configs/eval_gpt4o_vision_proper.toml +29 -0
- examples/qwen_vl/configs/eval_gpt5nano_vision.toml +45 -0
- examples/qwen_vl/configs/eval_qwen2vl_vision.toml +44 -0
- examples/qwen_vl/configs/filter_qwen2vl_sft.toml +50 -0
- examples/qwen_vl/configs/filter_vision_sft.toml +53 -0
- examples/qwen_vl/configs/filter_vision_test.toml +8 -0
- examples/qwen_vl/configs/sft_qwen3_vl_2b_test.toml +54 -0
- examples/qwen_vl/crafter_gpt5nano_agent.py +308 -0
- examples/qwen_vl/crafter_qwen_vl_agent.py +300 -0
- examples/qwen_vl/run_vision_comparison.sh +62 -0
- examples/qwen_vl/run_vision_sft_pipeline.sh +175 -0
- examples/qwen_vl/test_image_validation.py +201 -0
- examples/qwen_vl/test_sft_vision_data.py +110 -0
- examples/rl/README.md +1 -1
- examples/rl/configs/eval_base_qwen.toml +17 -0
- examples/rl/configs/eval_rl_qwen.toml +13 -0
- examples/rl/configs/rl_from_base_qwen.toml +37 -0
- examples/rl/configs/rl_from_base_qwen17.toml +76 -0
- examples/rl/configs/rl_from_ft_qwen.toml +37 -0
- examples/rl/run_eval.py +436 -0
- examples/rl/run_rl_and_save.py +111 -0
- examples/rl/task_app/README.md +22 -0
- examples/rl/task_app/math_single_step.py +990 -0
- examples/rl/task_app/math_task_app.py +111 -0
- examples/sft/README.md +5 -5
- examples/sft/configs/crafter_fft_qwen0p6b.toml +4 -2
- examples/sft/configs/crafter_lora_qwen0p6b.toml +4 -3
- examples/sft/evaluate.py +2 -4
- examples/sft/export_dataset.py +7 -4
- examples/swe/task_app/README.md +1 -1
- examples/swe/task_app/grpo_swe_mini.py +0 -1
- examples/swe/task_app/grpo_swe_mini_task_app.py +0 -12
- examples/swe/task_app/hosted/envs/mini_swe/environment.py +13 -13
- examples/swe/task_app/hosted/policy_routes.py +0 -2
- examples/swe/task_app/hosted/rollout.py +0 -8
- examples/task_apps/crafter/task_app/grpo_crafter.py +4 -7
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +59 -1
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +30 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +62 -31
- examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +16 -14
- examples/task_apps/enron/__init__.py +1 -0
- examples/vlm/README.md +3 -3
- examples/vlm/configs/crafter_vlm_gpt4o.toml +2 -0
- examples/vlm/crafter_openai_vlm_agent.py +3 -5
- examples/vlm/filter_image_rows.py +1 -1
- examples/vlm/run_crafter_vlm_benchmark.py +2 -2
- examples/warming_up_to_rl/_utils.py +92 -0
- examples/warming_up_to_rl/analyze_trace_db.py +1 -1
- examples/warming_up_to_rl/configs/crafter_fft.toml +2 -0
- examples/warming_up_to_rl/configs/crafter_fft_4b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +2 -1
- examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +2 -1
- examples/warming_up_to_rl/configs/rl_from_ft.toml +2 -0
- examples/warming_up_to_rl/export_trace_sft.py +174 -60
- examples/warming_up_to_rl/readme.md +63 -132
- examples/warming_up_to_rl/run_fft_and_save.py +1 -1
- examples/warming_up_to_rl/run_rl_and_save.py +1 -1
- examples/warming_up_to_rl/task_app/README.md +42 -0
- examples/warming_up_to_rl/task_app/grpo_crafter.py +696 -0
- examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +135 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/README.md +173 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +143 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +1226 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +6 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +522 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +478 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +108 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +305 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +47 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +204 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +618 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +100 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +1081 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +195 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +1861 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +211 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +161 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +137 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +62 -0
- synth_ai/__init__.py +44 -30
- synth_ai/_utils/__init__.py +47 -0
- synth_ai/_utils/base_url.py +10 -0
- synth_ai/_utils/http.py +10 -0
- synth_ai/_utils/prompts.py +10 -0
- synth_ai/_utils/task_app_state.py +12 -0
- synth_ai/_utils/user_config.py +10 -0
- synth_ai/api/models/supported.py +144 -7
- synth_ai/api/train/__init__.py +13 -1
- synth_ai/api/train/cli.py +30 -7
- synth_ai/api/train/config_finder.py +18 -11
- synth_ai/api/train/env_resolver.py +13 -10
- synth_ai/cli/__init__.py +62 -78
- synth_ai/cli/_modal_wrapper.py +7 -5
- synth_ai/cli/_typer_patch.py +0 -2
- synth_ai/cli/_validate_task_app.py +22 -4
- synth_ai/cli/legacy_root_backup.py +3 -1
- synth_ai/cli/lib/__init__.py +10 -0
- synth_ai/cli/lib/task_app_discovery.py +7 -0
- synth_ai/cli/lib/task_app_env.py +518 -0
- synth_ai/cli/recent.py +2 -1
- synth_ai/cli/setup.py +266 -0
- synth_ai/cli/status.py +1 -1
- synth_ai/cli/task_app_deploy.py +16 -0
- synth_ai/cli/task_app_list.py +25 -0
- synth_ai/cli/task_app_modal_serve.py +16 -0
- synth_ai/cli/task_app_serve.py +18 -0
- synth_ai/cli/task_apps.py +71 -31
- synth_ai/cli/traces.py +1 -1
- synth_ai/cli/train.py +18 -0
- synth_ai/cli/tui.py +7 -2
- synth_ai/cli/turso.py +1 -1
- synth_ai/cli/watch.py +1 -1
- synth_ai/demos/__init__.py +10 -0
- synth_ai/demos/core/__init__.py +28 -1
- synth_ai/demos/crafter/__init__.py +1 -0
- synth_ai/demos/crafter/crafter_fft_4b.toml +55 -0
- synth_ai/demos/crafter/grpo_crafter_task_app.py +185 -0
- synth_ai/demos/crafter/rl_from_base_qwen4b.toml +74 -0
- synth_ai/demos/demo_registry.py +176 -0
- synth_ai/demos/math/__init__.py +1 -0
- synth_ai/demos/math/_common.py +16 -0
- synth_ai/demos/math/app.py +38 -0
- synth_ai/demos/math/config.toml +76 -0
- synth_ai/demos/math/deploy_modal.py +54 -0
- synth_ai/demos/math/modal_task_app.py +702 -0
- synth_ai/demos/math/task_app_entry.py +51 -0
- synth_ai/environments/environment/core.py +7 -1
- synth_ai/environments/examples/bandit/engine.py +0 -1
- synth_ai/environments/examples/bandit/environment.py +0 -1
- synth_ai/environments/examples/wordle/environment.py +0 -1
- synth_ai/evals/base.py +16 -5
- synth_ai/evals/client.py +1 -1
- synth_ai/inference/client.py +1 -1
- synth_ai/judge_schemas.py +8 -8
- synth_ai/learning/client.py +1 -1
- synth_ai/learning/health.py +1 -1
- synth_ai/learning/jobs.py +1 -1
- synth_ai/learning/rl/client.py +1 -1
- synth_ai/learning/rl/env_keys.py +1 -1
- synth_ai/learning/rl/secrets.py +1 -1
- synth_ai/learning/sft/client.py +1 -1
- synth_ai/learning/sft/data.py +407 -4
- synth_ai/learning/validators.py +4 -1
- synth_ai/task/apps/__init__.py +4 -2
- synth_ai/task/config.py +6 -4
- synth_ai/task/rubrics/__init__.py +1 -2
- synth_ai/task/rubrics/loaders.py +14 -10
- synth_ai/task/rubrics.py +219 -0
- synth_ai/task/trace_correlation_helpers.py +24 -11
- synth_ai/task/tracing_utils.py +14 -3
- synth_ai/task/validators.py +2 -3
- synth_ai/tracing_v3/abstractions.py +3 -3
- synth_ai/tracing_v3/config.py +15 -13
- synth_ai/tracing_v3/constants.py +21 -0
- synth_ai/tracing_v3/db_config.py +3 -1
- synth_ai/tracing_v3/decorators.py +10 -7
- synth_ai/tracing_v3/llm_call_record_helpers.py +5 -5
- synth_ai/tracing_v3/session_tracer.py +7 -7
- synth_ai/tracing_v3/storage/base.py +29 -29
- synth_ai/tracing_v3/storage/config.py +3 -3
- synth_ai/tracing_v3/turso/daemon.py +8 -9
- synth_ai/tracing_v3/turso/native_manager.py +80 -72
- synth_ai/tracing_v3/utils.py +2 -2
- synth_ai/tui/cli/query_experiments.py +4 -4
- synth_ai/tui/cli/query_experiments_v3.py +4 -4
- synth_ai/tui/dashboard.py +14 -9
- synth_ai/utils/__init__.py +101 -0
- synth_ai/utils/base_url.py +94 -0
- synth_ai/utils/cli.py +131 -0
- synth_ai/utils/env.py +287 -0
- synth_ai/utils/http.py +169 -0
- synth_ai/utils/modal.py +308 -0
- synth_ai/utils/process.py +212 -0
- synth_ai/utils/prompts.py +39 -0
- synth_ai/utils/sqld.py +122 -0
- synth_ai/utils/task_app_discovery.py +882 -0
- synth_ai/utils/task_app_env.py +186 -0
- synth_ai/utils/task_app_state.py +318 -0
- synth_ai/utils/user_config.py +137 -0
- synth_ai/v0/config/__init__.py +1 -5
- synth_ai/v0/config/base_url.py +1 -7
- synth_ai/v0/tracing/config.py +1 -1
- synth_ai/v0/tracing/decorators.py +1 -1
- synth_ai/v0/tracing/upload.py +1 -1
- synth_ai/v0/tracing_v1/config.py +1 -1
- synth_ai/v0/tracing_v1/decorators.py +1 -1
- synth_ai/v0/tracing_v1/upload.py +1 -1
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/METADATA +85 -31
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/RECORD +229 -117
- synth_ai/cli/man.py +0 -106
- synth_ai/compound/cais.py +0 -0
- synth_ai/core/experiment.py +0 -13
- synth_ai/core/system.py +0 -15
- synth_ai/demo_registry.py +0 -295
- synth_ai/handshake.py +0 -109
- synth_ai/http.py +0 -26
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from .base_url import PROD_BASE_URL_DEFAULT
|
|
6
|
+
from .env import mask_str, resolve_env_var
|
|
7
|
+
from .process import ensure_local_port_available
|
|
8
|
+
from .task_app_state import persist_env_api_key
|
|
9
|
+
from .user_config import load_user_env, update_user_config
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ensure_env_credentials",
|
|
13
|
+
"ensure_port_free",
|
|
14
|
+
"preflight_env_key",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def ensure_env_credentials(*, require_synth: bool = False, prompt: bool = True) -> None:
|
|
19
|
+
"""Ensure required API keys are present in the process environment."""
|
|
20
|
+
|
|
21
|
+
load_user_env(override=False)
|
|
22
|
+
|
|
23
|
+
env_key = (os.environ.get("ENVIRONMENT_API_KEY") or "").strip()
|
|
24
|
+
if prompt and not env_key:
|
|
25
|
+
resolve_env_var("ENVIRONMENT_API_KEY")
|
|
26
|
+
env_key = (os.environ.get("ENVIRONMENT_API_KEY") or "").strip()
|
|
27
|
+
|
|
28
|
+
if env_key:
|
|
29
|
+
update_user_config(
|
|
30
|
+
{
|
|
31
|
+
"ENVIRONMENT_API_KEY": env_key,
|
|
32
|
+
"DEV_ENVIRONMENT_API_KEY": env_key,
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
persist_env_api_key(env_key)
|
|
36
|
+
elif prompt:
|
|
37
|
+
raise click.ClickException("ENVIRONMENT_API_KEY is required.")
|
|
38
|
+
|
|
39
|
+
synth_key = (os.environ.get("SYNTH_API_KEY") or "").strip()
|
|
40
|
+
if prompt and (require_synth or not synth_key):
|
|
41
|
+
resolve_env_var("SYNTH_API_KEY")
|
|
42
|
+
synth_key = (os.environ.get("SYNTH_API_KEY") or "").strip()
|
|
43
|
+
|
|
44
|
+
if synth_key:
|
|
45
|
+
update_user_config({"SYNTH_API_KEY": synth_key})
|
|
46
|
+
elif require_synth and prompt:
|
|
47
|
+
raise click.ClickException("SYNTH_API_KEY is required.")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def ensure_port_free(port: int, host: str, *, force: bool) -> None:
|
|
51
|
+
"""Ensure a TCP port is not in use, optionally killing processes when ``force`` is True."""
|
|
52
|
+
|
|
53
|
+
if ensure_local_port_available(host, port, force=force):
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
message = f"Port {port} is still in use. Stop the running server and try again."
|
|
57
|
+
if force:
|
|
58
|
+
raise click.ClickException(message)
|
|
59
|
+
raise click.ClickException(f"Port {port} appears to be in use. Restart with --force to terminate it.")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def preflight_env_key(*, crash_on_failure: bool = False) -> None:
|
|
63
|
+
"""Ensure ENVIRONMENT_API_KEY exists and attempt a backend registration."""
|
|
64
|
+
|
|
65
|
+
ensure_env_credentials(require_synth=False, prompt=not crash_on_failure)
|
|
66
|
+
load_user_env(override=False)
|
|
67
|
+
|
|
68
|
+
raw_backend = (
|
|
69
|
+
os.environ.get("BACKEND_BASE_URL")
|
|
70
|
+
or os.environ.get("SYNTH_BASE_URL")
|
|
71
|
+
or f"{PROD_BASE_URL_DEFAULT}/api"
|
|
72
|
+
)
|
|
73
|
+
backend_base = raw_backend.rstrip("/")
|
|
74
|
+
if not backend_base.endswith("/api"):
|
|
75
|
+
backend_base += "/api"
|
|
76
|
+
|
|
77
|
+
synth_key = os.environ.get("SYNTH_API_KEY") or ""
|
|
78
|
+
env_api_key = (os.environ.get("ENVIRONMENT_API_KEY") or "").strip()
|
|
79
|
+
|
|
80
|
+
def _mint_key() -> str | None:
|
|
81
|
+
try:
|
|
82
|
+
from synth_ai.learning.rl.secrets import mint_environment_api_key
|
|
83
|
+
|
|
84
|
+
key = mint_environment_api_key()
|
|
85
|
+
os.environ["ENVIRONMENT_API_KEY"] = key
|
|
86
|
+
os.environ.setdefault("DEV_ENVIRONMENT_API_KEY", key)
|
|
87
|
+
update_user_config(
|
|
88
|
+
{
|
|
89
|
+
"ENVIRONMENT_API_KEY": key,
|
|
90
|
+
"DEV_ENVIRONMENT_API_KEY": key,
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
persist_env_api_key(key)
|
|
94
|
+
click.echo(f"[preflight] minted ENVIRONMENT_API_KEY ({mask_str(key)})")
|
|
95
|
+
return key
|
|
96
|
+
except Exception as exc: # pragma: no cover - defensive fallback
|
|
97
|
+
if crash_on_failure:
|
|
98
|
+
raise click.ClickException(
|
|
99
|
+
f"[CRITICAL] Failed to mint ENVIRONMENT_API_KEY: {exc}"
|
|
100
|
+
) from exc
|
|
101
|
+
click.echo(
|
|
102
|
+
f"[WARN] Failed to mint ENVIRONMENT_API_KEY automatically ({exc}); proceeding without upload"
|
|
103
|
+
)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
minted = False
|
|
107
|
+
if not env_api_key:
|
|
108
|
+
env_api_key = _mint_key() or ""
|
|
109
|
+
minted = bool(env_api_key)
|
|
110
|
+
|
|
111
|
+
if not env_api_key:
|
|
112
|
+
if crash_on_failure:
|
|
113
|
+
raise click.ClickException(
|
|
114
|
+
"[CRITICAL] ENVIRONMENT_API_KEY missing; run `synth-ai setup` to configure it."
|
|
115
|
+
)
|
|
116
|
+
click.echo("[preflight] ENVIRONMENT_API_KEY missing; continuing without verification.")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
if minted:
|
|
120
|
+
persist_env_api_key(env_api_key)
|
|
121
|
+
|
|
122
|
+
if not synth_key.strip():
|
|
123
|
+
click.echo("[preflight] SYNTH_API_KEY not set; skipping backend preflight.")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
import base64
|
|
128
|
+
|
|
129
|
+
import httpx
|
|
130
|
+
from nacl.public import PublicKey, SealedBox
|
|
131
|
+
except Exception: # pragma: no cover - optional deps
|
|
132
|
+
click.echo("[preflight] Optional crypto dependencies missing; skipping upload.")
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
with httpx.Client(timeout=15.0, headers={"Authorization": f"Bearer {synth_key}"}) as client:
|
|
137
|
+
click.echo(f"[preflight] backend={backend_base}")
|
|
138
|
+
click.echo("[preflight] fetching public key…")
|
|
139
|
+
rpk = client.get(f"{backend_base.rstrip('/')}/v1/crypto/public-key")
|
|
140
|
+
if rpk.status_code != 200:
|
|
141
|
+
click.echo(f"[preflight] public key fetch failed with {rpk.status_code}; skipping upload")
|
|
142
|
+
return
|
|
143
|
+
pk = (rpk.json() or {}).get("public_key")
|
|
144
|
+
if not pk:
|
|
145
|
+
click.echo("[preflight] no public key returned; skipping upload")
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
pk_bytes = base64.b64decode(pk, validate=True)
|
|
149
|
+
sealed_box = SealedBox(PublicKey(pk_bytes))
|
|
150
|
+
ciphertext = sealed_box.encrypt(env_api_key.encode("utf-8"))
|
|
151
|
+
ct_b64 = base64.b64encode(ciphertext).decode()
|
|
152
|
+
payload = {"name": "ENVIRONMENT_API_KEY", "ciphertext_b64": ct_b64}
|
|
153
|
+
|
|
154
|
+
click.echo(f"[preflight] posting to {backend_base.rstrip('/')}/v1/env-keys")
|
|
155
|
+
response = client.post(f"{backend_base.rstrip('/')}/v1/env-keys", json=payload)
|
|
156
|
+
if 200 <= response.status_code < 300:
|
|
157
|
+
click.echo(
|
|
158
|
+
f"✅ ENVIRONMENT_API_KEY uploaded successfully ({mask_str(env_api_key)})"
|
|
159
|
+
)
|
|
160
|
+
try:
|
|
161
|
+
ver = client.get(f"{backend_base.rstrip('/')}/v1/env-keys/verify")
|
|
162
|
+
if ver.status_code == 200 and (ver.json() or {}).get("present"):
|
|
163
|
+
click.echo("✅ Key verified in backend")
|
|
164
|
+
else:
|
|
165
|
+
click.echo(
|
|
166
|
+
f"⚠️ Verification returned {ver.status_code}, but upload succeeded - proceeding"
|
|
167
|
+
)
|
|
168
|
+
except Exception as verify_err: # pragma: no cover - verification optional
|
|
169
|
+
click.echo(
|
|
170
|
+
f"⚠️ Verification check failed ({verify_err}), but upload succeeded - proceeding"
|
|
171
|
+
)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
snippet = response.text[:400] if response.text else ""
|
|
175
|
+
message = (
|
|
176
|
+
f"ENVIRONMENT_API_KEY upload failed with status {response.status_code}"
|
|
177
|
+
+ (f" body={snippet}" if snippet else "")
|
|
178
|
+
)
|
|
179
|
+
if crash_on_failure:
|
|
180
|
+
raise click.ClickException(f"[CRITICAL] {message}")
|
|
181
|
+
click.echo(f"[WARN] {message}; proceeding anyway")
|
|
182
|
+
except Exception as exc: # pragma: no cover - network failures
|
|
183
|
+
message = f"Backend preflight for ENVIRONMENT_API_KEY failed: {exc}"
|
|
184
|
+
if crash_on_failure:
|
|
185
|
+
raise click.ClickException(f"[CRITICAL] {message}") from exc
|
|
186
|
+
click.echo(f"[WARN] {message}; proceeding anyway")
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
DEFAULT_TASK_APP_SECRET_NAME = "synth-demo-task-app-secret"
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"DEFAULT_TASK_APP_SECRET_NAME",
|
|
12
|
+
"current_task_app_id",
|
|
13
|
+
"load_demo_dir",
|
|
14
|
+
"load_template_id",
|
|
15
|
+
"now_iso",
|
|
16
|
+
"persist_api_key",
|
|
17
|
+
"persist_demo_dir",
|
|
18
|
+
"persist_env_api_key",
|
|
19
|
+
"persist_task_url",
|
|
20
|
+
"persist_template_id",
|
|
21
|
+
"read_task_app_config",
|
|
22
|
+
"record_task_app",
|
|
23
|
+
"resolve_task_app_entry",
|
|
24
|
+
"task_app_config_path",
|
|
25
|
+
"task_app_id_from_path",
|
|
26
|
+
"update_task_app_entry",
|
|
27
|
+
"write_task_app_config",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def task_app_config_path() -> str:
|
|
32
|
+
return os.path.expanduser("~/.synth-ai/task_app_config.json")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def read_task_app_config() -> dict[str, Any]:
|
|
36
|
+
path = task_app_config_path()
|
|
37
|
+
try:
|
|
38
|
+
if os.path.isfile(path):
|
|
39
|
+
with open(path, encoding="utf-8") as handle:
|
|
40
|
+
loaded = json.load(handle) or {}
|
|
41
|
+
if isinstance(loaded, dict):
|
|
42
|
+
apps = loaded.get("apps")
|
|
43
|
+
if isinstance(apps, dict):
|
|
44
|
+
return apps
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def write_task_app_config(apps: dict[str, Any]) -> None:
|
|
51
|
+
payload = {"apps": apps}
|
|
52
|
+
try:
|
|
53
|
+
path = task_app_config_path()
|
|
54
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
55
|
+
with open(path, "w", encoding="utf-8") as handle:
|
|
56
|
+
json.dump(payload, handle, indent=2, sort_keys=True)
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def now_iso() -> str:
|
|
62
|
+
return (
|
|
63
|
+
datetime.now(UTC)
|
|
64
|
+
.replace(microsecond=0)
|
|
65
|
+
.isoformat()
|
|
66
|
+
.replace("+00:00", "Z")
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def task_app_id_from_path(path: str | Path | None) -> str | None:
|
|
71
|
+
if not path:
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
return str(Path(path).expanduser().resolve())
|
|
75
|
+
except Exception:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def current_task_app_id() -> str | None:
|
|
80
|
+
try:
|
|
81
|
+
return str(Path.cwd().resolve())
|
|
82
|
+
except Exception:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def update_task_app_entry(
|
|
87
|
+
path: str | Path | None,
|
|
88
|
+
*,
|
|
89
|
+
template_id: str | None = None,
|
|
90
|
+
mutate: Callable[[dict[str, Any]], None] | None = None,
|
|
91
|
+
) -> dict[str, Any]:
|
|
92
|
+
task_id = task_app_id_from_path(path)
|
|
93
|
+
if task_id is None:
|
|
94
|
+
task_id = current_task_app_id()
|
|
95
|
+
if task_id is None:
|
|
96
|
+
return {}
|
|
97
|
+
|
|
98
|
+
apps = read_task_app_config()
|
|
99
|
+
now = now_iso()
|
|
100
|
+
entry = apps.get(task_id)
|
|
101
|
+
if entry is None:
|
|
102
|
+
entry = {
|
|
103
|
+
"task_app_path": task_id,
|
|
104
|
+
"template_id": template_id,
|
|
105
|
+
"created_at": now,
|
|
106
|
+
"last_used": now,
|
|
107
|
+
"modal": {
|
|
108
|
+
"app_name": None,
|
|
109
|
+
"base_url": None,
|
|
110
|
+
"secret_name": DEFAULT_TASK_APP_SECRET_NAME,
|
|
111
|
+
"created_at": None,
|
|
112
|
+
"last_used": None,
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
apps[task_id] = entry
|
|
116
|
+
else:
|
|
117
|
+
entry.setdefault(
|
|
118
|
+
"modal",
|
|
119
|
+
{
|
|
120
|
+
"app_name": None,
|
|
121
|
+
"base_url": None,
|
|
122
|
+
"secret_name": DEFAULT_TASK_APP_SECRET_NAME,
|
|
123
|
+
"created_at": None,
|
|
124
|
+
"last_used": None,
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
if template_id is not None:
|
|
128
|
+
entry["template_id"] = template_id
|
|
129
|
+
|
|
130
|
+
if mutate is not None:
|
|
131
|
+
mutate(entry)
|
|
132
|
+
|
|
133
|
+
entry["last_used"] = now
|
|
134
|
+
write_task_app_config(apps)
|
|
135
|
+
return entry
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def record_task_app(
|
|
139
|
+
path: str,
|
|
140
|
+
*,
|
|
141
|
+
template_id: str | None = None,
|
|
142
|
+
secret_name: str | None = None,
|
|
143
|
+
) -> None:
|
|
144
|
+
|
|
145
|
+
def _mutate(entry: dict[str, Any]) -> None:
|
|
146
|
+
if secret_name:
|
|
147
|
+
modal_block = entry.setdefault(
|
|
148
|
+
"modal",
|
|
149
|
+
{
|
|
150
|
+
"app_name": None,
|
|
151
|
+
"base_url": None,
|
|
152
|
+
"secret_name": DEFAULT_TASK_APP_SECRET_NAME,
|
|
153
|
+
"created_at": None,
|
|
154
|
+
"last_used": None,
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
modal_block["secret_name"] = secret_name
|
|
158
|
+
|
|
159
|
+
update_task_app_entry(path, template_id=template_id, mutate=_mutate if secret_name else None)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _select_entry(
|
|
163
|
+
*,
|
|
164
|
+
preferred_path: str | None = None,
|
|
165
|
+
predicate: Callable[[dict[str, Any]], bool] | None = None,
|
|
166
|
+
) -> tuple[str | None, dict[str, Any]]:
|
|
167
|
+
entries = read_task_app_config()
|
|
168
|
+
if not entries:
|
|
169
|
+
return None, {}
|
|
170
|
+
|
|
171
|
+
def _matches(entry: dict[str, Any]) -> bool:
|
|
172
|
+
return predicate(entry) if predicate is not None else True
|
|
173
|
+
|
|
174
|
+
if preferred_path:
|
|
175
|
+
entry = entries.get(preferred_path)
|
|
176
|
+
if isinstance(entry, dict) and _matches(entry):
|
|
177
|
+
return preferred_path, entry
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
cwd = str(Path.cwd().resolve())
|
|
181
|
+
entry = entries.get(cwd)
|
|
182
|
+
if isinstance(entry, dict) and _matches(entry):
|
|
183
|
+
return cwd, entry
|
|
184
|
+
except Exception:
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
best_path: str | None = None
|
|
188
|
+
best_entry: dict[str, Any] = {}
|
|
189
|
+
best_ts = ""
|
|
190
|
+
for path, entry in entries.items():
|
|
191
|
+
if not isinstance(entry, dict) or not _matches(entry):
|
|
192
|
+
continue
|
|
193
|
+
ts = str(entry.get("last_used") or "")
|
|
194
|
+
if ts > best_ts:
|
|
195
|
+
best_ts = ts
|
|
196
|
+
best_path = path
|
|
197
|
+
best_entry = entry
|
|
198
|
+
return best_path, best_entry
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def resolve_task_app_entry(
|
|
202
|
+
preferred_path: str | None = None,
|
|
203
|
+
*,
|
|
204
|
+
predicate: Callable[[dict[str, Any]], bool] | None = None,
|
|
205
|
+
) -> tuple[str | None, dict[str, Any]]:
|
|
206
|
+
path, entry = _select_entry(preferred_path=preferred_path, predicate=predicate)
|
|
207
|
+
return path, entry if isinstance(entry, dict) else {}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def persist_demo_dir(demo_dir: str) -> None:
|
|
211
|
+
def _mutate(entry: dict[str, Any]) -> None:
|
|
212
|
+
entry["is_demo"] = True
|
|
213
|
+
|
|
214
|
+
update_task_app_entry(demo_dir, mutate=_mutate)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def load_demo_dir() -> str | None:
|
|
218
|
+
path, _ = _select_entry(predicate=lambda entry: entry.get("is_demo") or entry.get("template_id"))
|
|
219
|
+
return path
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def persist_template_id(template_id: str | None) -> None:
|
|
223
|
+
demo_dir = load_demo_dir() or current_task_app_id()
|
|
224
|
+
if demo_dir is None:
|
|
225
|
+
return
|
|
226
|
+
update_task_app_entry(demo_dir, template_id=template_id)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def load_template_id() -> str | None:
|
|
230
|
+
_, entry = _select_entry(predicate=lambda item: item.get("template_id"))
|
|
231
|
+
value = entry.get("template_id") if isinstance(entry, dict) else None
|
|
232
|
+
return str(value) if isinstance(value, str) else None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def persist_api_key(key: str) -> None:
|
|
236
|
+
target = load_demo_dir() or current_task_app_id()
|
|
237
|
+
|
|
238
|
+
def _mutate(entry: dict[str, Any]) -> None:
|
|
239
|
+
secrets = entry.setdefault("secrets", {})
|
|
240
|
+
secrets["synth_api_key"] = key
|
|
241
|
+
|
|
242
|
+
update_task_app_entry(target, mutate=_mutate)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def persist_env_api_key(key: str, path: str | Path | None = None) -> None:
|
|
246
|
+
target = path or load_demo_dir()
|
|
247
|
+
|
|
248
|
+
def _mutate(entry: dict[str, Any]) -> None:
|
|
249
|
+
secrets = entry.setdefault("secrets", {})
|
|
250
|
+
secrets["environment_api_key"] = key
|
|
251
|
+
|
|
252
|
+
update_task_app_entry(target, mutate=_mutate)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _derive_modal_app_name(url: str | None) -> str | None:
|
|
256
|
+
if not url:
|
|
257
|
+
return None
|
|
258
|
+
try:
|
|
259
|
+
from urllib.parse import urlparse
|
|
260
|
+
|
|
261
|
+
host = urlparse(url).hostname or ""
|
|
262
|
+
if "--" not in host:
|
|
263
|
+
return None
|
|
264
|
+
suffix = host.split("--", 1)[1]
|
|
265
|
+
core = suffix.split(".modal", 1)[0]
|
|
266
|
+
if core.endswith("-fastapi-app"):
|
|
267
|
+
core = core[: -len("-fastapi-app")]
|
|
268
|
+
return core.strip() or None
|
|
269
|
+
except Exception:
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def persist_task_url(url: str, *, name: str | None = None, path: str | None = None) -> None:
|
|
274
|
+
normalized_url = (url or "").rstrip("/")
|
|
275
|
+
task_id = task_app_id_from_path(path) or current_task_app_id()
|
|
276
|
+
if task_id is None:
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
existing = read_task_app_config().get(task_id, {})
|
|
280
|
+
previous_modal = dict(existing.get("modal", {})) if isinstance(existing, dict) else {}
|
|
281
|
+
|
|
282
|
+
derived_name = name or _derive_modal_app_name(normalized_url)
|
|
283
|
+
|
|
284
|
+
def _mutate(entry: dict[str, Any]) -> None:
|
|
285
|
+
modal_block = entry.setdefault(
|
|
286
|
+
"modal",
|
|
287
|
+
{
|
|
288
|
+
"app_name": None,
|
|
289
|
+
"base_url": None,
|
|
290
|
+
"secret_name": DEFAULT_TASK_APP_SECRET_NAME,
|
|
291
|
+
"created_at": None,
|
|
292
|
+
"last_used": None,
|
|
293
|
+
},
|
|
294
|
+
)
|
|
295
|
+
now = now_iso()
|
|
296
|
+
if modal_block.get("created_at") is None:
|
|
297
|
+
modal_block["created_at"] = now
|
|
298
|
+
modal_block["last_used"] = now
|
|
299
|
+
modal_block["base_url"] = normalized_url
|
|
300
|
+
if derived_name:
|
|
301
|
+
modal_block["app_name"] = derived_name
|
|
302
|
+
modal_block["secret_name"] = DEFAULT_TASK_APP_SECRET_NAME
|
|
303
|
+
|
|
304
|
+
entry = update_task_app_entry(path or task_id, mutate=_mutate)
|
|
305
|
+
|
|
306
|
+
modal_after = entry.get("modal", {}) if isinstance(entry, dict) else {}
|
|
307
|
+
changed: list[str] = []
|
|
308
|
+
if previous_modal.get("base_url") != modal_after.get("base_url"):
|
|
309
|
+
changed.append("TASK_APP_BASE_URL")
|
|
310
|
+
if derived_name and previous_modal.get("app_name") != modal_after.get("app_name"):
|
|
311
|
+
changed.append("TASK_APP_NAME")
|
|
312
|
+
if previous_modal.get("secret_name") != modal_after.get("secret_name"):
|
|
313
|
+
changed.append("TASK_APP_SECRET_NAME")
|
|
314
|
+
|
|
315
|
+
if changed:
|
|
316
|
+
print(f"Saved {', '.join(changed)} to {task_app_config_path()}")
|
|
317
|
+
if "TASK_APP_SECRET_NAME" in changed:
|
|
318
|
+
print(f"TASK_APP_SECRET_NAME={DEFAULT_TASK_APP_SECRET_NAME}")
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
CONFIG_DIR = Path(os.path.expanduser("~/.synth-ai"))
|
|
9
|
+
USER_CONFIG_PATH = CONFIG_DIR / "user_config.json"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _ensure_config_dir() -> None:
|
|
13
|
+
with contextlib.suppress(Exception):
|
|
14
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_user_config() -> dict[str, Any]:
|
|
18
|
+
"""Return the persisted user config as a dict (empty if missing or invalid)."""
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
if USER_CONFIG_PATH.is_file():
|
|
22
|
+
with USER_CONFIG_PATH.open("r", encoding="utf-8") as fh:
|
|
23
|
+
data = json.load(fh)
|
|
24
|
+
return data if isinstance(data, dict) else {}
|
|
25
|
+
except Exception:
|
|
26
|
+
return {}
|
|
27
|
+
return {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def save_user_config(config: Mapping[str, Any]) -> None:
|
|
31
|
+
"""Persist a new user config dictionary (overwrites previous contents)."""
|
|
32
|
+
|
|
33
|
+
_ensure_config_dir()
|
|
34
|
+
try:
|
|
35
|
+
with USER_CONFIG_PATH.open("w", encoding="utf-8") as fh:
|
|
36
|
+
json.dump(dict(config), fh, indent=2, sort_keys=True)
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def update_user_config(updates: Mapping[str, Any]) -> dict[str, Any]:
|
|
42
|
+
"""Merge `updates` into the existing user config and persist the result."""
|
|
43
|
+
|
|
44
|
+
current = load_user_config()
|
|
45
|
+
current.update(updates)
|
|
46
|
+
save_user_config(current)
|
|
47
|
+
return current
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _load_json(path: Path) -> dict[str, Any]:
|
|
51
|
+
try:
|
|
52
|
+
if path.is_file():
|
|
53
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
54
|
+
data = json.load(handle)
|
|
55
|
+
return data if isinstance(data, dict) else {}
|
|
56
|
+
except Exception:
|
|
57
|
+
return {}
|
|
58
|
+
return {}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _load_task_app_entries() -> dict[str, Any]:
|
|
62
|
+
data = _load_json(Path(os.path.expanduser("~/.synth-ai/task_app_config.json")))
|
|
63
|
+
if "apps" in data and isinstance(data["apps"], dict):
|
|
64
|
+
return data["apps"]
|
|
65
|
+
return {}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _select_task_app_entry(entries: dict[str, Any]) -> tuple[str | None, dict[str, Any]]:
|
|
69
|
+
if not entries:
|
|
70
|
+
return None, {}
|
|
71
|
+
|
|
72
|
+
demo_dir = os.environ.get("SYNTH_DEMO_DIR")
|
|
73
|
+
if demo_dir and demo_dir in entries:
|
|
74
|
+
return demo_dir, entries[demo_dir]
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
cwd = str(Path.cwd().resolve())
|
|
78
|
+
if cwd in entries:
|
|
79
|
+
return cwd, entries[cwd]
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
best_key = None
|
|
84
|
+
best_entry: dict[str, Any] = {}
|
|
85
|
+
best_ts = ""
|
|
86
|
+
for key, entry in entries.items():
|
|
87
|
+
if not isinstance(entry, dict):
|
|
88
|
+
continue
|
|
89
|
+
ts = str(entry.get("last_used") or "")
|
|
90
|
+
if ts > best_ts:
|
|
91
|
+
best_key = key
|
|
92
|
+
best_entry = entry
|
|
93
|
+
best_ts = ts
|
|
94
|
+
return best_key, best_entry
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def load_user_env(*, override: bool = True) -> dict[str, str]:
|
|
98
|
+
"""Hydrate ``os.environ`` from persisted Synth SDK state."""
|
|
99
|
+
|
|
100
|
+
applied: dict[str, str] = {}
|
|
101
|
+
|
|
102
|
+
def _apply(mapping: Mapping[str, Any]) -> None:
|
|
103
|
+
for key, value in mapping.items():
|
|
104
|
+
if value is None:
|
|
105
|
+
continue
|
|
106
|
+
str_value = value if isinstance(value, str) else str(value)
|
|
107
|
+
if override or key not in os.environ:
|
|
108
|
+
os.environ[key] = str_value
|
|
109
|
+
applied[key] = str_value
|
|
110
|
+
|
|
111
|
+
config = load_user_config()
|
|
112
|
+
_apply(config)
|
|
113
|
+
|
|
114
|
+
entry_key, entry = _select_task_app_entry(_load_task_app_entries())
|
|
115
|
+
if entry:
|
|
116
|
+
modal_block = entry.get("modal") if isinstance(entry.get("modal"), dict) else {}
|
|
117
|
+
if modal_block:
|
|
118
|
+
_apply(
|
|
119
|
+
{
|
|
120
|
+
"TASK_APP_BASE_URL": modal_block.get("base_url"),
|
|
121
|
+
"TASK_APP_NAME": modal_block.get("app_name"),
|
|
122
|
+
"TASK_APP_SECRET_NAME": modal_block.get("secret_name"),
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
if entry_key:
|
|
126
|
+
_apply({"SYNTH_DEMO_DIR": entry_key})
|
|
127
|
+
|
|
128
|
+
return applied
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
__all__ = [
|
|
132
|
+
"USER_CONFIG_PATH",
|
|
133
|
+
"load_user_config",
|
|
134
|
+
"save_user_config",
|
|
135
|
+
"update_user_config",
|
|
136
|
+
"load_user_env",
|
|
137
|
+
]
|
synth_ai/v0/config/__init__.py
CHANGED
|
@@ -3,13 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
# Compatibility package to mirror historical import paths.
|
|
4
4
|
# Re-export constants from the modern location under synth_ai.config.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from synth_ai.config.base_url import PROD_BASE_URL_DEFAULT as _PROD
|
|
8
|
-
except Exception: # pragma: no cover
|
|
9
|
-
_PROD = None
|
|
6
|
+
from synth_ai._utils.base_url import PROD_BASE_URL_DEFAULT as _PROD
|
|
10
7
|
|
|
11
8
|
__all__ = [
|
|
12
9
|
"_PROD",
|
|
13
10
|
]
|
|
14
11
|
|
|
15
|
-
|
synth_ai/v0/config/base_url.py
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
# Prefer the modern constant
|
|
5
|
-
from synth_ai.config.base_url import PROD_BASE_URL_DEFAULT as PROD_BASE_URL_DEFAULT # type: ignore
|
|
6
|
-
except Exception: # pragma: no cover
|
|
7
|
-
# Fallback if the modern module moves; provide a safe default
|
|
8
|
-
PROD_BASE_URL_DEFAULT = "https://agent-learning.onrender.com"
|
|
3
|
+
from synth_ai._utils.base_url import PROD_BASE_URL_DEFAULT
|
|
9
4
|
|
|
10
5
|
__all__ = ["PROD_BASE_URL_DEFAULT"]
|
|
11
6
|
|
|
12
|
-
|
synth_ai/v0/tracing/config.py
CHANGED
|
@@ -12,7 +12,7 @@ from opentelemetry.sdk.trace.export import (
|
|
|
12
12
|
)
|
|
13
13
|
from pydantic import BaseModel, ConfigDict, Field
|
|
14
14
|
|
|
15
|
-
from synth_ai.
|
|
15
|
+
from synth_ai._utils.base_url import PROD_BASE_URL_DEFAULT
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class InMemoryExporter(SpanExporter):
|
|
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Literal, ParamSpec, TypeVar, Union
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from .trackers import SynthTrackerAsync, SynthTrackerSync
|
|
12
12
|
|
|
13
|
-
from synth_ai.
|
|
13
|
+
from synth_ai._utils.base_url import PROD_BASE_URL_DEFAULT
|
|
14
14
|
|
|
15
15
|
from .abstractions import (
|
|
16
16
|
AgentComputeStep,
|
synth_ai/v0/tracing/upload.py
CHANGED
|
@@ -13,7 +13,7 @@ from pydantic import BaseModel, ConfigDict, field_validator
|
|
|
13
13
|
from requests.adapters import HTTPAdapter
|
|
14
14
|
from urllib3.poolmanager import PoolManager
|
|
15
15
|
|
|
16
|
-
from synth_ai.
|
|
16
|
+
from synth_ai._utils.base_url import PROD_BASE_URL_DEFAULT
|
|
17
17
|
|
|
18
18
|
from .abstractions import Dataset, SystemTrace
|
|
19
19
|
from .events.store import event_store
|