synth-ai 0.2.14__py3-none-any.whl → 0.4.1__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.
- synth_ai/__init__.py +19 -40
- synth_ai/__main__.py +30 -3
- synth_ai/cli/__init__.py +105 -70
- synth_ai/cli/__main__.py +42 -0
- synth_ai/cli/_internal/__init__.py +5 -0
- synth_ai/cli/_internal/modal_wrapper.py +31 -0
- synth_ai/cli/_internal/storage.py +20 -0
- synth_ai/cli/_internal/typer_patch.py +47 -0
- synth_ai/cli/_internal/validate_task_app.py +29 -0
- synth_ai/cli/agents/__init__.py +17 -0
- synth_ai/cli/agents/claude.py +77 -0
- synth_ai/cli/agents/codex.py +265 -0
- synth_ai/cli/agents/opencode.py +253 -0
- synth_ai/cli/commands/__init__.py +18 -0
- synth_ai/cli/commands/artifacts/__init__.py +13 -0
- synth_ai/cli/commands/artifacts/client.py +119 -0
- synth_ai/cli/commands/artifacts/config.py +57 -0
- synth_ai/cli/commands/artifacts/core.py +24 -0
- synth_ai/cli/commands/artifacts/download.py +188 -0
- synth_ai/cli/commands/artifacts/export.py +186 -0
- synth_ai/cli/commands/artifacts/list.py +156 -0
- synth_ai/cli/commands/artifacts/parsing.py +250 -0
- synth_ai/cli/commands/artifacts/show.py +336 -0
- synth_ai/cli/commands/baseline/__init__.py +12 -0
- synth_ai/cli/commands/baseline/core.py +636 -0
- synth_ai/cli/commands/baseline/list.py +94 -0
- synth_ai/cli/commands/demo/__init__.py +3 -0
- synth_ai/cli/commands/demo/core.py +153 -0
- synth_ai/cli/commands/eval/__init__.py +19 -0
- synth_ai/cli/commands/eval/core.py +1113 -0
- synth_ai/cli/commands/eval/errors.py +81 -0
- synth_ai/cli/commands/eval/validation.py +133 -0
- synth_ai/cli/commands/filter/__init__.py +12 -0
- synth_ai/cli/commands/filter/core.py +424 -0
- synth_ai/cli/commands/filter/errors.py +55 -0
- synth_ai/cli/commands/filter/validation.py +77 -0
- synth_ai/cli/commands/help/__init__.py +185 -0
- synth_ai/cli/commands/help/core.py +72 -0
- synth_ai/cli/commands/scan/__init__.py +19 -0
- synth_ai/cli/commands/scan/cloudflare_scanner.py +403 -0
- synth_ai/cli/commands/scan/core.py +344 -0
- synth_ai/cli/commands/scan/health_checker.py +242 -0
- synth_ai/cli/commands/scan/local_scanner.py +278 -0
- synth_ai/cli/commands/scan/models.py +83 -0
- synth_ai/cli/commands/smoke/__init__.py +7 -0
- synth_ai/cli/commands/smoke/core.py +1438 -0
- synth_ai/cli/commands/status/__init__.py +66 -0
- synth_ai/cli/commands/status/client.py +192 -0
- synth_ai/cli/commands/status/config.py +92 -0
- synth_ai/cli/commands/status/errors.py +20 -0
- synth_ai/cli/commands/status/formatters.py +164 -0
- synth_ai/cli/commands/status/subcommands/__init__.py +9 -0
- synth_ai/cli/commands/status/subcommands/files.py +79 -0
- synth_ai/cli/commands/status/subcommands/jobs.py +334 -0
- synth_ai/cli/commands/status/subcommands/models.py +79 -0
- synth_ai/cli/commands/status/subcommands/pricing.py +23 -0
- synth_ai/cli/commands/status/subcommands/runs.py +81 -0
- synth_ai/cli/commands/status/subcommands/session.py +182 -0
- synth_ai/cli/commands/status/subcommands/summary.py +47 -0
- synth_ai/cli/commands/status/subcommands/usage.py +203 -0
- synth_ai/cli/commands/status/utils.py +114 -0
- synth_ai/cli/commands/train/__init__.py +53 -0
- synth_ai/cli/commands/train/core.py +22 -0
- synth_ai/cli/commands/train/errors.py +117 -0
- synth_ai/cli/commands/train/judge_schemas.py +201 -0
- synth_ai/cli/commands/train/judge_validation.py +305 -0
- synth_ai/cli/commands/train/prompt_learning_validation.py +633 -0
- synth_ai/cli/commands/train/validation.py +392 -0
- synth_ai/cli/demo_apps/__init__.py +10 -0
- synth_ai/cli/demo_apps/core/__init__.py +28 -0
- synth_ai/cli/demo_apps/core/cli.py +1735 -0
- synth_ai/cli/demo_apps/crafter/crafter_fft_4b.toml +55 -0
- synth_ai/cli/demo_apps/crafter/grpo_crafter_task_app.py +186 -0
- synth_ai/cli/demo_apps/crafter/rl_from_base_qwen4b.toml +74 -0
- synth_ai/cli/demo_apps/demo_registry.py +176 -0
- synth_ai/cli/demo_apps/demo_task_apps/core.py +440 -0
- synth_ai/cli/demo_apps/demo_task_apps/crafter/__init__.py +1 -0
- synth_ai/cli/demo_apps/demo_task_apps/crafter/grpo_crafter_task_app.py +185 -0
- synth_ai/cli/demo_apps/demo_task_apps/math/modal_task_app.py +742 -0
- synth_ai/cli/demo_apps/demo_task_apps/math/task_app_entry.py +39 -0
- synth_ai/cli/demo_apps/math/__init__.py +1 -0
- synth_ai/cli/demo_apps/math/_common.py +16 -0
- synth_ai/cli/demo_apps/math/app.py +38 -0
- synth_ai/cli/demo_apps/math/config.toml +76 -0
- synth_ai/cli/demo_apps/math/deploy_modal.py +54 -0
- synth_ai/cli/demo_apps/math/modal_task_app.py +702 -0
- synth_ai/cli/demo_apps/math/task_app_entry.py +53 -0
- synth_ai/cli/demo_apps/mipro/main.py +271 -0
- synth_ai/cli/demo_apps/mipro/task_app.py +933 -0
- synth_ai/cli/demo_apps/mipro/train_cfg.toml +92 -0
- synth_ai/cli/demos/__init__.py +12 -0
- synth_ai/cli/demos/demo.py +32 -0
- synth_ai/cli/demos/rl_demo.py +254 -0
- synth_ai/cli/deploy.py +216 -0
- synth_ai/cli/infra/__init__.py +14 -0
- synth_ai/cli/infra/balance.py +216 -0
- synth_ai/cli/infra/mcp.py +35 -0
- synth_ai/cli/infra/modal_app.py +36 -0
- synth_ai/cli/infra/setup.py +69 -0
- synth_ai/cli/infra/status.py +16 -0
- synth_ai/cli/infra/turso.py +77 -0
- synth_ai/cli/lib/__init__.py +10 -0
- synth_ai/cli/lib/agents.py +76 -0
- synth_ai/cli/lib/apps/modal_app.py +101 -0
- synth_ai/cli/lib/apps/task_app.py +643 -0
- synth_ai/cli/lib/bin.py +39 -0
- synth_ai/cli/lib/env.py +375 -0
- synth_ai/cli/lib/errors.py +85 -0
- synth_ai/cli/lib/modal.py +315 -0
- synth_ai/cli/lib/plotting.py +126 -0
- synth_ai/cli/lib/prompt_args.py +39 -0
- synth_ai/cli/lib/prompts.py +284 -0
- synth_ai/cli/lib/sqld.py +122 -0
- synth_ai/cli/lib/task_app_discovery.py +884 -0
- synth_ai/cli/lib/task_app_env.py +295 -0
- synth_ai/cli/lib/train_cfgs.py +300 -0
- synth_ai/cli/lib/tunnel_records.py +207 -0
- synth_ai/cli/local/__init__.py +14 -0
- synth_ai/cli/local/experiment_queue/__init__.py +72 -0
- synth_ai/cli/local/experiment_queue/api_schemas.py +221 -0
- synth_ai/cli/local/experiment_queue/celery_app.py +208 -0
- synth_ai/cli/local/experiment_queue/config.py +128 -0
- synth_ai/cli/local/experiment_queue/config_utils.py +272 -0
- synth_ai/cli/local/experiment_queue/database.py +175 -0
- synth_ai/cli/local/experiment_queue/dispatcher.py +119 -0
- synth_ai/cli/local/experiment_queue/models.py +231 -0
- synth_ai/cli/local/experiment_queue/progress_info.py +160 -0
- synth_ai/cli/local/experiment_queue/results.py +373 -0
- synth_ai/cli/local/experiment_queue/schemas.py +131 -0
- synth_ai/cli/local/experiment_queue/service.py +344 -0
- synth_ai/cli/local/experiment_queue/status.py +372 -0
- synth_ai/cli/local/experiment_queue/status_tracker.py +360 -0
- synth_ai/cli/local/experiment_queue/tasks.py +1984 -0
- synth_ai/cli/local/experiment_queue/trace_storage.py +65 -0
- synth_ai/cli/local/experiment_queue/validation.py +157 -0
- synth_ai/cli/local/session/__init__.py +92 -0
- synth_ai/cli/local/session/client.py +383 -0
- synth_ai/cli/local/session/constants.py +63 -0
- synth_ai/cli/local/session/exceptions.py +105 -0
- synth_ai/cli/local/session/manager.py +139 -0
- synth_ai/cli/local/session/models.py +89 -0
- synth_ai/cli/local/session/query.py +110 -0
- synth_ai/cli/root.py +30 -6
- synth_ai/cli/task_apps/__init__.py +26 -0
- synth_ai/cli/task_apps/commands.py +3153 -0
- synth_ai/cli/task_apps/deploy.py +7 -0
- synth_ai/cli/task_apps/list.py +26 -0
- synth_ai/cli/task_apps/main.py +36 -0
- synth_ai/cli/task_apps/modal_serve.py +11 -0
- synth_ai/cli/task_apps/serve.py +11 -0
- synth_ai/cli/training/__init__.py +8 -0
- synth_ai/cli/training/train.py +5 -0
- synth_ai/cli/training/train_cfg.py +34 -0
- synth_ai/cli/training/watch.py +506 -0
- synth_ai/cli/turso.py +34 -55
- synth_ai/cli/usage.py +159 -0
- synth_ai/cli/utils/__init__.py +8 -0
- synth_ai/cli/utils/experiments.py +235 -0
- synth_ai/cli/utils/queue.py +504 -0
- synth_ai/cli/utils/recent.py +133 -0
- synth_ai/cli/utils/traces.py +164 -0
- synth_ai/contracts/__init__.py +67 -0
- synth_ai/core/__init__.py +100 -0
- synth_ai/core/_utils/__init__.py +54 -0
- synth_ai/core/_utils/base_url.py +10 -0
- synth_ai/core/_utils/http.py +10 -0
- synth_ai/core/_utils/prompts.py +14 -0
- synth_ai/core/_utils/task_app_state.py +12 -0
- synth_ai/core/_utils/user_config.py +10 -0
- synth_ai/core/apps/common.py +116 -0
- synth_ai/core/auth.py +95 -0
- synth_ai/core/cfgs.py +240 -0
- synth_ai/core/config/__init__.py +16 -0
- synth_ai/core/config/base.py +168 -0
- synth_ai/core/config/resolver.py +89 -0
- synth_ai/core/env.py +220 -0
- synth_ai/core/errors.py +126 -0
- synth_ai/core/http.py +230 -0
- synth_ai/core/integrations/__init__.py +11 -0
- synth_ai/core/integrations/cloudflare.py +1710 -0
- synth_ai/core/integrations/mcp/__init__.py +6 -0
- synth_ai/core/integrations/mcp/__main__.py +8 -0
- synth_ai/core/integrations/mcp/claude.py +36 -0
- synth_ai/core/integrations/mcp/main.py +254 -0
- synth_ai/core/integrations/mcp/setup.py +100 -0
- synth_ai/core/integrations/modal.py +277 -0
- synth_ai/core/json.py +72 -0
- synth_ai/core/log_filter.py +99 -0
- synth_ai/core/logging.py +82 -0
- synth_ai/core/paths.py +107 -0
- synth_ai/core/pricing.py +109 -0
- synth_ai/core/process.py +233 -0
- synth_ai/core/ssl.py +25 -0
- synth_ai/core/storage/__init__.py +71 -0
- synth_ai/core/task_app_state.py +318 -0
- synth_ai/core/telemetry.py +282 -0
- synth_ai/core/tracing_v3/__init__.py +99 -0
- synth_ai/core/tracing_v3/abstractions.py +302 -0
- synth_ai/core/tracing_v3/config.py +229 -0
- synth_ai/core/tracing_v3/constants.py +21 -0
- synth_ai/core/tracing_v3/db_config.py +182 -0
- synth_ai/core/tracing_v3/decorators.py +401 -0
- synth_ai/core/tracing_v3/llm_call_record_helpers.py +437 -0
- synth_ai/core/tracing_v3/migration_helper.py +119 -0
- synth_ai/core/tracing_v3/session_tracer.py +542 -0
- synth_ai/core/tracing_v3/storage/base.py +211 -0
- synth_ai/core/tracing_v3/storage/config.py +109 -0
- synth_ai/core/tracing_v3/storage/factory.py +39 -0
- synth_ai/core/tracing_v3/trace_utils.py +326 -0
- synth_ai/core/tracing_v3/turso/daemon.py +278 -0
- synth_ai/core/tracing_v3/turso/models.py +470 -0
- synth_ai/core/tracing_v3/turso/native_manager.py +1385 -0
- synth_ai/core/tracing_v3/utils.py +108 -0
- synth_ai/core/urls.py +18 -0
- synth_ai/core/user_config.py +137 -0
- synth_ai/core/uvicorn.py +222 -0
- synth_ai/data/__init__.py +110 -0
- synth_ai/data/enums.py +141 -0
- synth_ai/data/rewards.py +152 -0
- synth_ai/data/specs.py +36 -0
- synth_ai/data/traces.py +35 -0
- synth_ai/products/__init__.py +6 -0
- synth_ai/products/graph_evolve/__init__.py +46 -0
- synth_ai/products/graph_evolve/client.py +226 -0
- synth_ai/products/graph_evolve/config.py +591 -0
- synth_ai/products/graph_evolve/converters/__init__.py +42 -0
- synth_ai/products/graph_evolve/converters/openai_sft.py +484 -0
- synth_ai/products/graph_evolve/examples/hotpotqa/config.toml +109 -0
- synth_ai/products/graph_evolve/run.py +222 -0
- synth_ai/sdk/__init__.py +119 -0
- synth_ai/sdk/api/__init__.py +1 -0
- synth_ai/sdk/api/models/supported.py +514 -0
- synth_ai/sdk/api/research_agent/__init__.py +86 -0
- synth_ai/sdk/api/research_agent/cli.py +428 -0
- synth_ai/sdk/api/research_agent/config.py +357 -0
- synth_ai/sdk/api/research_agent/job.py +717 -0
- synth_ai/sdk/api/train/__init__.py +85 -0
- synth_ai/sdk/api/train/builders.py +895 -0
- synth_ai/sdk/api/train/cli.py +2188 -0
- synth_ai/sdk/api/train/config_finder.py +267 -0
- synth_ai/sdk/api/train/configs/__init__.py +65 -0
- synth_ai/sdk/api/train/configs/prompt_learning.py +1706 -0
- synth_ai/sdk/api/train/configs/rl.py +188 -0
- synth_ai/sdk/api/train/configs/sft.py +99 -0
- synth_ai/sdk/api/train/configs/shared.py +81 -0
- synth_ai/sdk/api/train/context_learning.py +312 -0
- synth_ai/sdk/api/train/env_resolver.py +418 -0
- synth_ai/sdk/api/train/graph_validators.py +216 -0
- synth_ai/sdk/api/train/graphgen.py +984 -0
- synth_ai/sdk/api/train/graphgen_models.py +823 -0
- synth_ai/sdk/api/train/graphgen_validators.py +109 -0
- synth_ai/sdk/api/train/pollers.py +124 -0
- synth_ai/sdk/api/train/progress/__init__.py +97 -0
- synth_ai/sdk/api/train/progress/dataclasses.py +569 -0
- synth_ai/sdk/api/train/progress/events.py +326 -0
- synth_ai/sdk/api/train/progress/results.py +428 -0
- synth_ai/sdk/api/train/progress/tracker.py +641 -0
- synth_ai/sdk/api/train/prompt_learning.py +470 -0
- synth_ai/sdk/api/train/rl.py +442 -0
- synth_ai/sdk/api/train/sft.py +396 -0
- synth_ai/sdk/api/train/summary.py +522 -0
- synth_ai/sdk/api/train/supported_algos.py +147 -0
- synth_ai/sdk/api/train/task_app.py +331 -0
- synth_ai/sdk/api/train/utils.py +279 -0
- synth_ai/sdk/api/train/validators.py +2424 -0
- synth_ai/sdk/baseline/__init__.py +25 -0
- synth_ai/sdk/baseline/config.py +209 -0
- synth_ai/sdk/baseline/discovery.py +216 -0
- synth_ai/sdk/baseline/execution.py +154 -0
- synth_ai/sdk/graphs/__init__.py +15 -0
- synth_ai/sdk/graphs/completions.py +570 -0
- synth_ai/sdk/inference/__init__.py +6 -0
- synth_ai/sdk/inference/client.py +128 -0
- synth_ai/sdk/jobs/__init__.py +16 -0
- synth_ai/sdk/jobs/client.py +371 -0
- synth_ai/sdk/judging/__init__.py +15 -0
- synth_ai/sdk/judging/base.py +24 -0
- synth_ai/sdk/judging/client.py +191 -0
- synth_ai/sdk/judging/schemas.py +222 -0
- synth_ai/sdk/learning/__init__.py +69 -0
- synth_ai/sdk/learning/client.py +240 -0
- synth_ai/sdk/learning/ft_client.py +7 -0
- synth_ai/sdk/learning/health.py +49 -0
- synth_ai/sdk/learning/jobs.py +202 -0
- synth_ai/sdk/learning/prompt_extraction.py +334 -0
- synth_ai/sdk/learning/prompt_learning_client.py +455 -0
- synth_ai/sdk/learning/prompt_learning_types.py +185 -0
- synth_ai/sdk/learning/rl/client.py +268 -0
- synth_ai/sdk/learning/rl/contracts.py +27 -0
- synth_ai/sdk/learning/rl/env_keys.py +166 -0
- synth_ai/sdk/learning/rl/secrets.py +13 -0
- synth_ai/sdk/learning/sft/client.py +95 -0
- synth_ai/sdk/learning/sft/config.py +270 -0
- synth_ai/sdk/learning/sft/data.py +698 -0
- synth_ai/sdk/learning/validators.py +52 -0
- synth_ai/sdk/research_agent/__init__.py +34 -0
- synth_ai/sdk/research_agent/container_builder.py +328 -0
- synth_ai/sdk/research_agent/container_spec.py +198 -0
- synth_ai/sdk/research_agent/defaults.py +34 -0
- synth_ai/sdk/research_agent/results_collector.py +69 -0
- synth_ai/sdk/specs/__init__.py +46 -0
- synth_ai/sdk/specs/dataclasses.py +149 -0
- synth_ai/sdk/specs/loader.py +144 -0
- synth_ai/sdk/specs/serializer.py +199 -0
- synth_ai/sdk/specs/validation.py +250 -0
- synth_ai/sdk/streaming/__init__.py +35 -0
- synth_ai/sdk/streaming/config.py +94 -0
- synth_ai/sdk/streaming/handlers.py +1997 -0
- synth_ai/sdk/streaming/streamer.py +704 -0
- synth_ai/sdk/streaming/types.py +112 -0
- synth_ai/sdk/task/__init__.py +151 -0
- synth_ai/sdk/task/apps/__init__.py +133 -0
- synth_ai/sdk/task/config.py +261 -0
- synth_ai/sdk/task/contracts.py +298 -0
- synth_ai/sdk/task/datasets.py +108 -0
- synth_ai/sdk/task/in_process.py +1190 -0
- synth_ai/sdk/task/in_process_runner.py +309 -0
- synth_ai/sdk/task/inference_api.py +299 -0
- synth_ai/sdk/task/proxy.py +287 -0
- synth_ai/sdk/task/rubrics/__init__.py +55 -0
- synth_ai/sdk/task/rubrics/loaders.py +156 -0
- synth_ai/sdk/task/rubrics.py +219 -0
- synth_ai/sdk/task/server.py +580 -0
- synth_ai/sdk/task/trace_correlation_helpers.py +506 -0
- synth_ai/sdk/task/tracing_utils.py +95 -0
- synth_ai/sdk/task/validators.py +456 -0
- synth_ai/sdk/tracing/__init__.py +39 -0
- synth_ai/sdk/training/__init__.py +102 -0
- synth_ai/sdk/usage/__init__.py +37 -0
- synth_ai/sdk/usage/client.py +171 -0
- synth_ai/sdk/usage/models.py +261 -0
- synth_ai/utils/__init__.py +213 -0
- synth_ai-0.4.1.dist-info/METADATA +195 -0
- synth_ai-0.4.1.dist-info/RECORD +379 -0
- synth_ai-0.4.1.dist-info/top_level.txt +1 -0
- examples/__init__.py +0 -16
- examples/analyze_semantic_words.sh +0 -17
- examples/crafter_debug_render.py +0 -186
- examples/dev/qwen3_32b_qlora_4xh100.toml +0 -40
- examples/multi_step/configs/README_verilog_rl.md +0 -77
- examples/multi_step/configs/VERILOG_REWARDS.md +0 -90
- examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +0 -183
- examples/multi_step/configs/crafter_eval_synth_qwen4b.toml +0 -35
- examples/multi_step/configs/crafter_eval_text_only_groq_qwen32b.toml +0 -36
- examples/multi_step/configs/crafter_rl_outcome.toml +0 -74
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +0 -187
- examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +0 -83
- examples/multi_step/configs/crafter_rl_stepwise_simple.toml +0 -78
- examples/multi_step/configs/crafter_synth_backend.md +0 -40
- examples/multi_step/configs/verilog_eval_groq_qwen32b.toml +0 -31
- examples/multi_step/configs/verilog_eval_synth_qwen8b.toml +0 -33
- examples/multi_step/configs/verilog_rl_lora.toml +0 -190
- examples/multi_step/crafter_rl_lora.md +0 -70
- examples/multi_step/judges/crafter_backend_judge.py +0 -220
- examples/multi_step/judges/verilog_backend_judge.py +0 -234
- examples/multi_step/readme.md +0 -48
- examples/multi_step/sse_metrics_streaming_notes.md +0 -357
- examples/multi_step/task_app_config_notes.md +0 -494
- examples/multi_step/verilog_rl_lora.md +0 -218
- examples/qwen_coder/README.md +0 -102
- examples/qwen_coder/_shared.py +0 -113
- examples/qwen_coder/configs/coder_lora_30b.toml +0 -61
- examples/qwen_coder/configs/coder_lora_4b.toml +0 -57
- examples/qwen_coder/configs/coder_lora_small.toml +0 -58
- examples/qwen_coder/generate_dataset.py +0 -98
- examples/qwen_coder/infer_ft_smoke.py +0 -65
- examples/qwen_coder/infer_prod_proxy.py +0 -73
- examples/qwen_coder/infer_via_synth.py +0 -87
- examples/qwen_coder/scripts/infer_coder.sh +0 -19
- examples/qwen_coder/scripts/train_coder_30b.sh +0 -22
- examples/qwen_coder/sft_full_17b.py +0 -103
- examples/qwen_coder/sft_lora_30b.py +0 -110
- examples/qwen_coder/subset_jsonl.py +0 -39
- examples/qwen_coder/todos.md +0 -38
- examples/qwen_coder/validate_jsonl.py +0 -60
- examples/rl/README.md +0 -169
- examples/rl/download_dataset.py +0 -80
- examples/run_crafter_demo.sh +0 -10
- examples/sft/README.md +0 -139
- examples/sft/configs/crafter_fft_qwen0p6b.toml +0 -44
- examples/sft/configs/crafter_lora_qwen0p6b.toml +0 -45
- examples/sft/evaluate.py +0 -119
- examples/sft/export_dataset.py +0 -117
- examples/sft/generate_traces.py +0 -164
- examples/swe/__init__.py +0 -12
- examples/swe/task_app/README.md +0 -105
- examples/swe/task_app/__init__.py +0 -2
- examples/swe/task_app/grpo_swe_mini.py +0 -601
- examples/swe/task_app/grpo_swe_mini_task_app.py +0 -136
- examples/swe/task_app/hosted/README.md +0 -173
- examples/swe/task_app/hosted/__init__.py +0 -5
- examples/swe/task_app/hosted/branching.py +0 -143
- examples/swe/task_app/hosted/environment_routes.py +0 -1289
- examples/swe/task_app/hosted/envs/__init__.py +0 -1
- examples/swe/task_app/hosted/envs/crafter/__init__.py +0 -6
- examples/swe/task_app/hosted/envs/crafter/app.py +0 -1
- examples/swe/task_app/hosted/envs/crafter/environment.py +0 -522
- examples/swe/task_app/hosted/envs/crafter/policy.py +0 -478
- examples/swe/task_app/hosted/envs/crafter/react_agent.py +0 -108
- examples/swe/task_app/hosted/envs/crafter/shared.py +0 -305
- examples/swe/task_app/hosted/envs/crafter/tools.py +0 -47
- examples/swe/task_app/hosted/envs/mini_swe/__init__.py +0 -8
- examples/swe/task_app/hosted/envs/mini_swe/environment.py +0 -1164
- examples/swe/task_app/hosted/envs/mini_swe/policy.py +0 -355
- examples/swe/task_app/hosted/envs/mini_swe/shared.py +0 -83
- examples/swe/task_app/hosted/envs/mini_swe/tools.py +0 -96
- examples/swe/task_app/hosted/hosted_app.py +0 -204
- examples/swe/task_app/hosted/inference/__init__.py +0 -5
- examples/swe/task_app/hosted/inference/openai_client.py +0 -618
- examples/swe/task_app/hosted/main.py +0 -100
- examples/swe/task_app/hosted/policy_routes.py +0 -1079
- examples/swe/task_app/hosted/registry.py +0 -195
- examples/swe/task_app/hosted/rollout.py +0 -1911
- examples/swe/task_app/hosted/storage/__init__.py +0 -5
- examples/swe/task_app/hosted/storage/volume.py +0 -211
- examples/swe/task_app/hosted/test_agents.py +0 -161
- examples/swe/task_app/hosted/test_service.py +0 -136
- examples/swe/task_app/hosted/utils.py +0 -62
- examples/task_apps/IMAGE_ONLY_EVAL_QUICKSTART.md +0 -258
- examples/task_apps/TESTING.md +0 -275
- examples/task_apps/crafter/CREATE_SFT_DATASET.md +0 -273
- examples/task_apps/crafter/EVAL_IMAGE_ONLY_RESULTS.md +0 -152
- examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +0 -174
- examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +0 -268
- examples/task_apps/crafter/QUERY_EXAMPLES.md +0 -203
- examples/task_apps/crafter/README_IMAGE_ONLY_EVAL.md +0 -316
- examples/task_apps/crafter/__init__.py +0 -0
- examples/task_apps/crafter/eval_image_only_gpt4o.toml +0 -28
- examples/task_apps/crafter/eval_text_only_groq_llama.toml +0 -36
- examples/task_apps/crafter/filter_sft_dataset.toml +0 -16
- examples/task_apps/crafter/task_app/README.md +0 -42
- examples/task_apps/crafter/task_app/__init__.py +0 -5
- examples/task_apps/crafter/task_app/grpo_crafter.py +0 -973
- examples/task_apps/crafter/task_app/grpo_crafter_task_app.py +0 -146
- examples/task_apps/crafter/task_app/synth_envs_hosted/README.md +0 -173
- examples/task_apps/crafter/task_app/synth_envs_hosted/__init__.py +0 -5
- examples/task_apps/crafter/task_app/synth_envs_hosted/branching.py +0 -143
- examples/task_apps/crafter/task_app/synth_envs_hosted/environment_routes.py +0 -1226
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/__init__.py +0 -1
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/__init__.py +0 -6
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/app.py +0 -1
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/environment.py +0 -532
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +0 -547
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +0 -123
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/shared.py +0 -305
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/tools.py +0 -47
- examples/task_apps/crafter/task_app/synth_envs_hosted/hosted_app.py +0 -204
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/__init__.py +0 -5
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +0 -704
- examples/task_apps/crafter/task_app/synth_envs_hosted/main.py +0 -100
- examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +0 -1152
- examples/task_apps/crafter/task_app/synth_envs_hosted/registry.py +0 -195
- examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +0 -2160
- examples/task_apps/crafter/task_app/synth_envs_hosted/storage/__init__.py +0 -5
- examples/task_apps/crafter/task_app/synth_envs_hosted/storage/volume.py +0 -211
- examples/task_apps/crafter/task_app/synth_envs_hosted/test_agents.py +0 -161
- examples/task_apps/crafter/task_app/synth_envs_hosted/test_service.py +0 -136
- examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +0 -218
- examples/task_apps/dev/pokemon_emerald/__init__.py +0 -2
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/README.md +0 -811
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/__init__.py +0 -120
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/action.py +0 -160
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/memory.py +0 -155
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/perception.py +0 -69
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/planning.py +0 -96
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/simple.py +0 -1502
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/system_prompt.py +0 -4
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/grab_map.py +0 -68
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/manual.py +0 -216
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/__init__.py +0 -35
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emerald_utils.py +0 -631
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emulator.py +0 -1544
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/enums.py +0 -1428
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/memory_reader.py +0 -4848
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/types.py +0 -41
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/utils.py +0 -298
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pyproject.toml +0 -95
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/run.py +0 -204
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/__init__.py +0 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/app.py +0 -2152
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/client.py +0 -429
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/frame_server.py +0 -155
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/README.md +0 -78
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/__init__.py +0 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/run_tests.py +0 -122
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_direct.py +0 -76
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_prompts.py +0 -413
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_battle_state_formatting.py +0 -204
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection.py +0 -133
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection_comprehensive.py +0 -229
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_direct_agent_emulator.py +0 -300
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_fps_adjustment_pytest.py +0 -205
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_direct.py +0 -200
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_transition.py +0 -284
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_map_ground_truth_comparison.py +0 -468
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_memory_map.py +0 -575
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_server_map_validation.py +0 -311
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_torchic_state.py +0 -259
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/__init__.py +0 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/anticheat.py +0 -372
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/checkpoint.py +0 -296
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/error_handler.py +0 -275
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/get_local_ip.py +0 -22
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/helpers.py +0 -44
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/llm_logger.py +0 -514
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_formatter.py +0 -415
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher.py +0 -1763
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher_singleton.py +0 -33
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_trimmer.py +0 -106
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_visualizer.py +0 -334
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/ocr_dialogue.py +0 -1020
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/recording.py +0 -188
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/state_formatter.py +0 -1481
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/vlm.py +0 -862
- examples/task_apps/dev/pokemon_emerald/modal_app.py +0 -114
- examples/task_apps/dev/pokemon_emerald/task_app/README.md +0 -81
- examples/task_apps/dev/pokemon_emerald/task_app/__init__.py +0 -6
- examples/task_apps/dev/pokemon_emerald/task_app/pokemon_emerald.py +0 -685
- examples/task_apps/enron/__init__.py +0 -1
- examples/task_apps/enron/eval_groq_qwen32.toml +0 -16
- examples/task_apps/enron/filter_sft.toml +0 -5
- examples/task_apps/enron/task_app/README.md +0 -14
- examples/task_apps/enron/task_app/__init__.py +0 -1
- examples/task_apps/enron/task_app/grpo_enron.py +0 -906
- examples/task_apps/enron/task_app/grpo_enron_task_app.py +0 -146
- examples/task_apps/enron/tests/__init__.py +0 -4
- examples/task_apps/enron/tests/conftest.py +0 -115
- examples/task_apps/enron/tests/integration/__init__.py +0 -4
- examples/task_apps/enron/tests/integration/test_enron_eval.py +0 -179
- examples/task_apps/enron/tests/integration/test_enron_rollout.py +0 -135
- examples/task_apps/enron/tests/unit/__init__.py +0 -4
- examples/task_apps/enron/tests/unit/test_enron_environment.py +0 -126
- examples/task_apps/math/README.md +0 -22
- examples/task_apps/math/__init__.py +0 -0
- examples/task_apps/math/math_single_step.py +0 -1000
- examples/task_apps/math/math_task_app.py +0 -115
- examples/task_apps/pokemon_battle/__init__.py +0 -2
- examples/task_apps/pokemon_battle/modal_app.py +0 -104
- examples/task_apps/pokemon_battle/task_app/README.md +0 -68
- examples/task_apps/pokemon_battle/task_app/__init__.py +0 -6
- examples/task_apps/pokemon_battle/task_app/pokemon_showdown.py +0 -932
- examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_COMPLETE.md +0 -283
- examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_STATUS.md +0 -155
- examples/task_apps/pokemon_red/README.md +0 -357
- examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +0 -415
- examples/task_apps/pokemon_red/__init__.py +0 -3
- examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +0 -29
- examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +0 -225
- examples/task_apps/pokemon_red/pallet_town_rl_config.toml +0 -75
- examples/task_apps/pokemon_red/task_app.py +0 -799
- examples/task_apps/pokemon_red/test_pallet_town_rewards.py +0 -193
- examples/task_apps/sokoban/README.md +0 -307
- examples/task_apps/sokoban/__init__.py +0 -3
- examples/task_apps/sokoban/eval_groq_qwen32.toml +0 -16
- examples/task_apps/sokoban/eval_openai_gpt5.toml +0 -16
- examples/task_apps/sokoban/filter_sft.toml +0 -5
- examples/task_apps/sokoban/task_app.py +0 -1058
- examples/task_apps/sokoban/tests/__init__.py +0 -4
- examples/task_apps/sokoban/tests/conftest.py +0 -113
- examples/task_apps/sokoban/tests/integration/__init__.py +0 -4
- examples/task_apps/sokoban/tests/integration/test_sokoban_eval.py +0 -57
- examples/task_apps/sokoban/tests/integration/test_sokoban_rollout.py +0 -198
- examples/task_apps/sokoban/tests/unit/__init__.py +0 -4
- examples/task_apps/sokoban/tests/unit/test_sokoban_environment.py +0 -114
- examples/task_apps/verilog/__init__.py +0 -1
- examples/task_apps/verilog/eval_groq_qwen32b.toml +0 -24
- examples/task_apps/verilog/filter_sft.toml +0 -5
- examples/task_apps/verilog/task_app/README.md +0 -12
- examples/task_apps/verilog/task_app/__init__.py +0 -1
- examples/task_apps/verilog/task_app/grpo_verilog.py +0 -1166
- examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +0 -145
- examples/task_apps/verilog/tests/__init__.py +0 -4
- examples/task_apps/verilog/tests/conftest.py +0 -115
- examples/task_apps/verilog/tests/integration/__init__.py +0 -4
- examples/task_apps/verilog/tests/integration/test_verilog_eval.py +0 -181
- examples/task_apps/verilog/tests/integration/test_verilog_rollout.py +0 -55
- examples/task_apps/verilog/tests/unit/__init__.py +0 -4
- examples/task_apps/verilog/tests/unit/test_verilog_scoring.py +0 -118
- examples/vlm/PROPOSAL.md +0 -53
- examples/vlm/README.md +0 -68
- examples/vlm/configs/crafter_vlm_gpt4o.toml +0 -44
- examples/vlm/crafter_image_only_agent.py +0 -207
- examples/vlm/crafter_openai_vlm_agent.py +0 -277
- examples/vlm/filter_image_rows.py +0 -63
- examples/vlm/run_crafter_vlm_benchmark.py +0 -316
- examples/warming_up_to_rl/analyze_trace_db.py +0 -422
- examples/warming_up_to_rl/configs/crafter_fft.toml +0 -48
- examples/warming_up_to_rl/configs/crafter_fft_4b.toml +0 -54
- examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +0 -20
- examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +0 -13
- examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +0 -23
- examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +0 -35
- examples/warming_up_to_rl/configs/eval_stepwise_consistent.toml +0 -26
- examples/warming_up_to_rl/configs/eval_stepwise_per_achievement.toml +0 -36
- examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +0 -32
- examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +0 -83
- examples/warming_up_to_rl/configs/rl_from_ft.toml +0 -56
- examples/warming_up_to_rl/export_trace_sft.py +0 -723
- examples/warming_up_to_rl/groq_test.py +0 -97
- examples/warming_up_to_rl/manage_secrets.py +0 -131
- examples/warming_up_to_rl/old/event_rewards.md +0 -234
- examples/warming_up_to_rl/old/notes.md +0 -73
- examples/warming_up_to_rl/readme.md +0 -179
- examples/warming_up_to_rl/run_eval.py +0 -736
- examples/warming_up_to_rl/run_fft_and_save.py +0 -380
- examples/warming_up_to_rl/run_local_rollout.py +0 -239
- examples/warming_up_to_rl/run_local_rollout_modal.py +0 -248
- examples/warming_up_to_rl/run_local_rollout_parallel.py +0 -405
- examples/warming_up_to_rl/run_local_rollout_traced.py +0 -477
- examples/warming_up_to_rl/run_rl_and_save.py +0 -124
- examples/warming_up_to_rl/run_rollout_remote.py +0 -156
- examples/workflows/__init__.py +0 -0
- examples/workflows/math_rl/__init__.py +0 -0
- examples/workflows/math_rl/configs/eval_base_qwen.toml +0 -15
- examples/workflows/math_rl/configs/eval_rl_qwen.toml +0 -11
- examples/workflows/math_rl/configs/rl_from_base_qwen.toml +0 -35
- examples/workflows/math_rl/configs/rl_from_base_qwen17.toml +0 -74
- examples/workflows/math_rl/configs/rl_from_ft_qwen.toml +0 -35
- examples/workflows/math_rl/download_dataset.py +0 -80
- examples/workflows/math_rl/run_eval.py +0 -436
- examples/workflows/math_rl/run_rl_and_save.py +0 -111
- synth_ai/api/models/supported.py +0 -377
- synth_ai/api/train/__init__.py +0 -5
- synth_ai/api/train/builders.py +0 -351
- synth_ai/api/train/cli.py +0 -635
- synth_ai/api/train/config_finder.py +0 -228
- synth_ai/api/train/configs/__init__.py +0 -44
- synth_ai/api/train/configs/rl.py +0 -134
- synth_ai/api/train/configs/sft.py +0 -95
- synth_ai/api/train/configs/shared.py +0 -24
- synth_ai/api/train/env_resolver.py +0 -349
- synth_ai/api/train/pollers.py +0 -75
- synth_ai/api/train/supported_algos.py +0 -147
- synth_ai/api/train/task_app.py +0 -195
- synth_ai/api/train/utils.py +0 -225
- synth_ai/cli/_modal_wrapper.py +0 -29
- synth_ai/cli/_storage.py +0 -20
- synth_ai/cli/_typer_patch.py +0 -49
- synth_ai/cli/_validate_task_app.py +0 -11
- synth_ai/cli/balance.py +0 -216
- synth_ai/cli/calc.py +0 -84
- synth_ai/cli/demo.py +0 -165
- synth_ai/cli/legacy_root_backup.py +0 -468
- synth_ai/cli/man.py +0 -106
- synth_ai/cli/recent.py +0 -132
- synth_ai/cli/rl_demo.py +0 -254
- synth_ai/cli/status.py +0 -134
- synth_ai/cli/task_apps.py +0 -4523
- synth_ai/cli/traces.py +0 -164
- synth_ai/cli/tui.py +0 -57
- synth_ai/cli/watch.py +0 -506
- synth_ai/compound/cais.py +0 -0
- synth_ai/config/base_url.py +0 -107
- synth_ai/core/experiment.py +0 -13
- synth_ai/core/system.py +0 -15
- synth_ai/demo_registry.py +0 -295
- synth_ai/demos/core/__init__.py +0 -1
- synth_ai/demos/core/cli.py +0 -1718
- synth_ai/demos/demo_task_apps/core.py +0 -440
- synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +0 -184
- synth_ai/demos/demo_task_apps/math/deploy_task_app.sh +0 -22
- synth_ai/demos/demo_task_apps/math/modal_task_app.py +0 -739
- synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -37
- synth_ai/environments/__init__.py +0 -31
- synth_ai/environments/environment/__init__.py +0 -1
- synth_ai/environments/environment/artifacts/__init__.py +0 -1
- synth_ai/environments/environment/artifacts/base.py +0 -52
- synth_ai/environments/environment/core.py +0 -67
- synth_ai/environments/environment/db/__init__.py +0 -1
- synth_ai/environments/environment/db/sqlite.py +0 -45
- synth_ai/environments/environment/registry.py +0 -233
- synth_ai/environments/environment/resources/sqlite.py +0 -45
- synth_ai/environments/environment/results.py +0 -1
- synth_ai/environments/environment/rewards/__init__.py +0 -1
- synth_ai/environments/environment/rewards/core.py +0 -29
- synth_ai/environments/environment/shared_engine.py +0 -26
- synth_ai/environments/environment/tools/__init__.py +0 -200
- synth_ai/environments/examples/__init__.py +0 -1
- synth_ai/environments/examples/bandit/__init__.py +0 -33
- synth_ai/environments/examples/bandit/engine.py +0 -302
- synth_ai/environments/examples/bandit/environment.py +0 -194
- synth_ai/environments/examples/bandit/taskset.py +0 -200
- synth_ai/environments/examples/crafter_classic/__init__.py +0 -8
- synth_ai/environments/examples/crafter_classic/agent_demos/analyze_semantic_words_markdown.py +0 -250
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +0 -59
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +0 -152
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_config.toml +0 -24
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +0 -1194
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/crafter_synth_config.toml +0 -56
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_config_modal.toml +0 -32
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/kick_off_ft_modal.py +0 -384
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_action_results.py +0 -53
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_agent_actions.py +0 -178
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_latest_run.py +0 -222
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_lm_traces.py +0 -183
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_no_rewards.py +0 -210
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_trace_issue.py +0 -206
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_db_schema.py +0 -49
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_latest_results.py +0 -64
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/debug_agent_responses.py +0 -88
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/quick_trace_check.py +0 -77
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/compare_experiments.py +0 -324
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/kick_off_ft_oai.py +0 -362
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/multi_model_config.toml +0 -49
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_enhanced_hooks.py +0 -332
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_events.py +0 -97
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_results.py +0 -217
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_hook_storage.py +0 -87
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_seeds.py +0 -88
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/compare_seed_performance.py +0 -195
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/custom_eval_pipelines.py +0 -400
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/plot_hook_frequency.py +0 -195
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/seed_analysis_summary.py +0 -56
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/run_rollouts_for_models_and_compare_v3.py +0 -858
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +0 -52
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +0 -874
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +0 -1412
- synth_ai/environments/examples/crafter_classic/agent_demos/example_v3_usage.py +0 -216
- synth_ai/environments/examples/crafter_classic/agent_demos/old/compare_traces.py +0 -296
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_comprehensive_evaluation.py +0 -58
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_env_serialization.py +0 -464
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_evaluation_browser.py +0 -152
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_quick_evaluation.py +0 -51
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_trace_evaluation.py +0 -1412
- synth_ai/environments/examples/crafter_classic/agent_demos/old/debug_player_loss.py +0 -112
- synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_service.py +0 -203
- synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_slowness.py +0 -305
- synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_by_difficulty.py +0 -126
- synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_example.py +0 -94
- synth_ai/environments/examples/crafter_classic/agent_demos/old/explore_saved_states.py +0 -142
- synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft.py +0 -26
- synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft_OLD.py +0 -984
- synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_gemini.py +0 -724
- synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_modal.py +0 -386
- synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_metadata.py +0 -205
- synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_gemini.py +0 -150
- synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_modal.py +0 -283
- synth_ai/environments/examples/crafter_classic/agent_demos/old/prepare_vertex_ft.py +0 -280
- synth_ai/environments/examples/crafter_classic/agent_demos/old/profile_env_slowness.py +0 -456
- synth_ai/environments/examples/crafter_classic/agent_demos/old/replicate_issue.py +0 -166
- synth_ai/environments/examples/crafter_classic/agent_demos/old/run_and_eval.py +0 -102
- synth_ai/environments/examples/crafter_classic/agent_demos/old/run_comparison.py +0 -128
- synth_ai/environments/examples/crafter_classic/agent_demos/old/run_qwen_rollouts.py +0 -655
- synth_ai/environments/examples/crafter_classic/agent_demos/old/trace_eval_OLD.py +0 -202
- synth_ai/environments/examples/crafter_classic/agent_demos/old/validate_openai_format.py +0 -166
- synth_ai/environments/examples/crafter_classic/config_logging.py +0 -111
- synth_ai/environments/examples/crafter_classic/debug_translation.py +0 -0
- synth_ai/environments/examples/crafter_classic/engine.py +0 -579
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +0 -64
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +0 -6
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +0 -75
- synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +0 -267
- synth_ai/environments/examples/crafter_classic/environment.py +0 -495
- synth_ai/environments/examples/crafter_classic/taskset.py +0 -233
- synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +0 -228
- synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +0 -299
- synth_ai/environments/examples/crafter_custom/__init__.py +0 -4
- synth_ai/environments/examples/crafter_custom/agent_demos/__init__.py +0 -1
- synth_ai/environments/examples/crafter_custom/agent_demos/trace_eval.py +0 -202
- synth_ai/environments/examples/crafter_custom/crafter/__init__.py +0 -7
- synth_ai/environments/examples/crafter_custom/crafter/config.py +0 -182
- synth_ai/environments/examples/crafter_custom/crafter/constants.py +0 -8
- synth_ai/environments/examples/crafter_custom/crafter/engine.py +0 -269
- synth_ai/environments/examples/crafter_custom/crafter/env.py +0 -262
- synth_ai/environments/examples/crafter_custom/crafter/objects.py +0 -417
- synth_ai/environments/examples/crafter_custom/crafter/recorder.py +0 -187
- synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +0 -118
- synth_ai/environments/examples/crafter_custom/dataset_builder.py +0 -373
- synth_ai/environments/examples/crafter_custom/environment.py +0 -312
- synth_ai/environments/examples/crafter_custom/old/analyze_diamond_issue.py +0 -159
- synth_ai/environments/examples/crafter_custom/old/analyze_diamond_spawning.py +0 -158
- synth_ai/environments/examples/crafter_custom/old/compare_worlds.py +0 -71
- synth_ai/environments/examples/crafter_custom/old/dataset_stats.py +0 -105
- synth_ai/environments/examples/crafter_custom/old/diamond_spawning_summary.py +0 -119
- synth_ai/environments/examples/crafter_custom/old/example_dataset_usage.py +0 -52
- synth_ai/environments/examples/crafter_custom/run_dataset.py +0 -305
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +0 -156
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +0 -281
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +0 -25
- synth_ai/environments/examples/enron/engine.py +0 -300
- synth_ai/environments/examples/enron/environment.py +0 -234
- synth_ai/environments/examples/enron/taskset.py +0 -112
- synth_ai/environments/examples/enron/units/keyword_stats.py +0 -112
- synth_ai/environments/examples/minigrid/__init__.py +0 -48
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +0 -1188
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +0 -48
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +0 -562
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +0 -221
- synth_ai/environments/examples/minigrid/engine.py +0 -589
- synth_ai/environments/examples/minigrid/environment.py +0 -274
- synth_ai/environments/examples/minigrid/environment_mapping.py +0 -242
- synth_ai/environments/examples/minigrid/puzzle_loader.py +0 -417
- synth_ai/environments/examples/minigrid/taskset.py +0 -583
- synth_ai/environments/examples/nethack/__init__.py +0 -7
- synth_ai/environments/examples/nethack/achievements.py +0 -337
- synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +0 -981
- synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +0 -74
- synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +0 -831
- synth_ai/environments/examples/nethack/engine.py +0 -739
- synth_ai/environments/examples/nethack/environment.py +0 -256
- synth_ai/environments/examples/nethack/helpers/__init__.py +0 -41
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +0 -301
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +0 -402
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +0 -433
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +0 -200
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +0 -269
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +0 -308
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +0 -431
- synth_ai/environments/examples/nethack/taskset.py +0 -323
- synth_ai/environments/examples/red/__init__.py +0 -7
- synth_ai/environments/examples/red/agent_demos/__init__.py +0 -1
- synth_ai/environments/examples/red/config_logging.py +0 -110
- synth_ai/environments/examples/red/engine.py +0 -721
- synth_ai/environments/examples/red/engine_helpers/__init__.py +0 -1
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +0 -35
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +0 -276
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +0 -142
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +0 -57
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +0 -284
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +0 -150
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +0 -138
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +0 -57
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +0 -331
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +0 -121
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_progression.py +0 -477
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +0 -559
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +0 -313
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +0 -148
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +0 -247
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +0 -368
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +0 -172
- synth_ai/environments/examples/red/environment.py +0 -298
- synth_ai/environments/examples/red/taskset.py +0 -79
- synth_ai/environments/examples/red/units/__init__.py +0 -1
- synth_ai/environments/examples/sokoban/__init__.py +0 -1
- synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +0 -899
- synth_ai/environments/examples/sokoban/engine.py +0 -678
- synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +0 -1
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +0 -657
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +0 -18
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +0 -3
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +0 -131
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +0 -370
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +0 -332
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +0 -306
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +0 -67
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +0 -115
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +0 -123
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +0 -394
- synth_ai/environments/examples/sokoban/environment.py +0 -229
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +0 -440
- synth_ai/environments/examples/sokoban/puzzle_loader.py +0 -312
- synth_ai/environments/examples/sokoban/taskset.py +0 -544
- synth_ai/environments/examples/tictactoe/__init__.py +0 -1
- synth_ai/environments/examples/tictactoe/engine.py +0 -368
- synth_ai/environments/examples/tictactoe/environment.py +0 -240
- synth_ai/environments/examples/tictactoe/taskset.py +0 -215
- synth_ai/environments/examples/verilog/__init__.py +0 -10
- synth_ai/environments/examples/verilog/engine.py +0 -421
- synth_ai/environments/examples/verilog/environment.py +0 -350
- synth_ai/environments/examples/verilog/taskset.py +0 -420
- synth_ai/environments/examples/wordle/__init__.py +0 -29
- synth_ai/environments/examples/wordle/engine.py +0 -398
- synth_ai/environments/examples/wordle/environment.py +0 -159
- synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +0 -75
- synth_ai/environments/examples/wordle/taskset.py +0 -230
- synth_ai/environments/reproducibility/core.py +0 -42
- synth_ai/environments/reproducibility/helpers.py +0 -0
- synth_ai/environments/reproducibility/tree.py +0 -363
- synth_ai/environments/service/app.py +0 -97
- synth_ai/environments/service/core_routes.py +0 -1021
- synth_ai/environments/service/external_registry.py +0 -56
- synth_ai/environments/service/registry.py +0 -9
- synth_ai/environments/stateful/__init__.py +0 -1
- synth_ai/environments/stateful/core.py +0 -163
- synth_ai/environments/stateful/engine.py +0 -21
- synth_ai/environments/stateful/state.py +0 -7
- synth_ai/environments/tasks/api.py +0 -19
- synth_ai/environments/tasks/core.py +0 -81
- synth_ai/environments/tasks/filters.py +0 -40
- synth_ai/environments/tasks/utils.py +0 -90
- synth_ai/environments/v0_observability/history.py +0 -3
- synth_ai/environments/v0_observability/log.py +0 -2
- synth_ai/evals/__init__.py +0 -15
- synth_ai/evals/base.py +0 -13
- synth_ai/evals/client.py +0 -82
- synth_ai/handshake.py +0 -109
- synth_ai/http.py +0 -26
- synth_ai/http_client.py +0 -136
- synth_ai/inference/__init__.py +0 -5
- synth_ai/inference/client.py +0 -34
- synth_ai/jobs/client.py +0 -295
- synth_ai/judge_schemas.py +0 -127
- synth_ai/learning/__init__.py +0 -59
- synth_ai/learning/client.py +0 -241
- synth_ai/learning/ft_client.py +0 -7
- synth_ai/learning/health.py +0 -49
- synth_ai/learning/jobs.py +0 -201
- synth_ai/learning/rl/client.py +0 -267
- synth_ai/learning/rl/contracts.py +0 -27
- synth_ai/learning/rl/env_keys.py +0 -166
- synth_ai/learning/rl/secrets.py +0 -13
- synth_ai/learning/sft/client.py +0 -68
- synth_ai/learning/sft/config.py +0 -270
- synth_ai/learning/sft/data.py +0 -295
- synth_ai/learning/validators.py +0 -49
- synth_ai/lm/__init__.py +0 -25
- synth_ai/task/__init__.py +0 -121
- synth_ai/task/apps/__init__.py +0 -129
- synth_ai/task/config.py +0 -257
- synth_ai/task/contracts.py +0 -236
- synth_ai/task/datasets.py +0 -108
- synth_ai/task/proxy.py +0 -251
- synth_ai/task/rubrics/__init__.py +0 -56
- synth_ai/task/rubrics/loaders.py +0 -152
- synth_ai/task/server.py +0 -432
- synth_ai/task/trace_correlation_helpers.py +0 -315
- synth_ai/task/tracing_utils.py +0 -84
- synth_ai/task/validators.py +0 -418
- synth_ai/tracing_v3/__init__.py +0 -97
- synth_ai/tracing_v3/abstractions.py +0 -302
- synth_ai/tracing_v3/config.py +0 -84
- synth_ai/tracing_v3/db_config.py +0 -194
- synth_ai/tracing_v3/decorators.py +0 -398
- synth_ai/tracing_v3/llm_call_record_helpers.py +0 -391
- synth_ai/tracing_v3/migration_helper.py +0 -120
- synth_ai/tracing_v3/session_tracer.py +0 -540
- synth_ai/tracing_v3/storage/base.py +0 -210
- synth_ai/tracing_v3/storage/config.py +0 -75
- synth_ai/tracing_v3/storage/factory.py +0 -39
- synth_ai/tracing_v3/trace_utils.py +0 -317
- synth_ai/tracing_v3/turso/daemon.py +0 -151
- synth_ai/tracing_v3/turso/models.py +0 -469
- synth_ai/tracing_v3/turso/native_manager.py +0 -1209
- synth_ai/tracing_v3/utils.py +0 -108
- synth_ai/tui/__init__.py +0 -5
- synth_ai/tui/__main__.py +0 -13
- synth_ai/tui/cli/__init__.py +0 -1
- synth_ai/tui/cli/query_experiments.py +0 -164
- synth_ai/tui/cli/query_experiments_v3.py +0 -164
- synth_ai/tui/dashboard.py +0 -906
- synth_ai/v0/api/__init__.py +0 -8
- synth_ai/v0/api/models/__init__.py +0 -8
- synth_ai/v0/api/models/supported.py +0 -8
- synth_ai/v0/config/__init__.py +0 -15
- synth_ai/v0/config/base_url.py +0 -12
- synth_ai/v0/lm/__init__.py +0 -51
- synth_ai/v0/lm/caching/__init__.py +0 -0
- synth_ai/v0/lm/caching/constants.py +0 -6
- synth_ai/v0/lm/caching/dbs.py +0 -0
- synth_ai/v0/lm/caching/ephemeral.py +0 -100
- synth_ai/v0/lm/caching/handler.py +0 -137
- synth_ai/v0/lm/caching/initialize.py +0 -11
- synth_ai/v0/lm/caching/persistent.py +0 -114
- synth_ai/v0/lm/config.py +0 -115
- synth_ai/v0/lm/constants.py +0 -32
- synth_ai/v0/lm/core/__init__.py +0 -8
- synth_ai/v0/lm/core/all.py +0 -73
- synth_ai/v0/lm/core/exceptions.py +0 -5
- synth_ai/v0/lm/core/main.py +0 -331
- synth_ai/v0/lm/core/main_v3.py +0 -594
- synth_ai/v0/lm/core/synth_models.py +0 -35
- synth_ai/v0/lm/core/vendor_clients.py +0 -190
- synth_ai/v0/lm/cost/__init__.py +0 -0
- synth_ai/v0/lm/cost/monitor.py +0 -1
- synth_ai/v0/lm/cost/statefulness.py +0 -1
- synth_ai/v0/lm/injection.py +0 -80
- synth_ai/v0/lm/overrides.py +0 -206
- synth_ai/v0/lm/provider_support/__init__.py +0 -8
- synth_ai/v0/lm/provider_support/anthropic.py +0 -972
- synth_ai/v0/lm/provider_support/openai.py +0 -1139
- synth_ai/v0/lm/provider_support/suppress_logging.py +0 -31
- synth_ai/v0/lm/structured_outputs/__init__.py +0 -0
- synth_ai/v0/lm/structured_outputs/handler.py +0 -440
- synth_ai/v0/lm/structured_outputs/inject.py +0 -297
- synth_ai/v0/lm/structured_outputs/rehabilitate.py +0 -185
- synth_ai/v0/lm/tools/__init__.py +0 -3
- synth_ai/v0/lm/tools/base.py +0 -172
- synth_ai/v0/lm/unified_interface.py +0 -202
- synth_ai/v0/lm/vendors/__init__.py +0 -0
- synth_ai/v0/lm/vendors/base.py +0 -81
- synth_ai/v0/lm/vendors/core/__init__.py +0 -0
- synth_ai/v0/lm/vendors/core/anthropic_api.py +0 -387
- synth_ai/v0/lm/vendors/core/gemini_api.py +0 -292
- synth_ai/v0/lm/vendors/core/mistral_api.py +0 -322
- synth_ai/v0/lm/vendors/core/openai_api.py +0 -227
- synth_ai/v0/lm/vendors/core/synth_dev_api.py +0 -0
- synth_ai/v0/lm/vendors/local/__init__.py +0 -0
- synth_ai/v0/lm/vendors/local/ollama.py +0 -0
- synth_ai/v0/lm/vendors/openai_standard.py +0 -782
- synth_ai/v0/lm/vendors/openai_standard_responses.py +0 -259
- synth_ai/v0/lm/vendors/retries.py +0 -22
- synth_ai/v0/lm/vendors/supported/__init__.py +0 -0
- synth_ai/v0/lm/vendors/supported/custom_endpoint.py +0 -415
- synth_ai/v0/lm/vendors/supported/deepseek.py +0 -69
- synth_ai/v0/lm/vendors/supported/grok.py +0 -75
- synth_ai/v0/lm/vendors/supported/groq.py +0 -16
- synth_ai/v0/lm/vendors/supported/ollama.py +0 -15
- synth_ai/v0/lm/vendors/supported/openrouter.py +0 -74
- synth_ai/v0/lm/vendors/supported/together.py +0 -11
- synth_ai/v0/lm/vendors/synth_client.py +0 -835
- synth_ai/v0/lm/warmup.py +0 -186
- synth_ai/v0/tracing/__init__.py +0 -0
- synth_ai/v0/tracing/abstractions.py +0 -224
- synth_ai/v0/tracing/base_client.py +0 -91
- synth_ai/v0/tracing/client_manager.py +0 -131
- synth_ai/v0/tracing/config.py +0 -142
- synth_ai/v0/tracing/context.py +0 -146
- synth_ai/v0/tracing/decorators.py +0 -682
- synth_ai/v0/tracing/events/__init__.py +0 -0
- synth_ai/v0/tracing/events/manage.py +0 -147
- synth_ai/v0/tracing/events/scope.py +0 -86
- synth_ai/v0/tracing/events/store.py +0 -228
- synth_ai/v0/tracing/immediate_client.py +0 -151
- synth_ai/v0/tracing/local.py +0 -18
- synth_ai/v0/tracing/log_client_base.py +0 -73
- synth_ai/v0/tracing/retry_queue.py +0 -186
- synth_ai/v0/tracing/trackers.py +0 -515
- synth_ai/v0/tracing/upload.py +0 -409
- synth_ai/v0/tracing/utils.py +0 -9
- synth_ai/v0/tracing_v1/__init__.py +0 -16
- synth_ai/v0/tracing_v1/abstractions.py +0 -224
- synth_ai/v0/tracing_v1/base_client.py +0 -91
- synth_ai/v0/tracing_v1/client_manager.py +0 -131
- synth_ai/v0/tracing_v1/config.py +0 -142
- synth_ai/v0/tracing_v1/context.py +0 -146
- synth_ai/v0/tracing_v1/decorators.py +0 -703
- synth_ai/v0/tracing_v1/events/__init__.py +0 -0
- synth_ai/v0/tracing_v1/events/manage.py +0 -147
- synth_ai/v0/tracing_v1/events/scope.py +0 -86
- synth_ai/v0/tracing_v1/events/store.py +0 -228
- synth_ai/v0/tracing_v1/immediate_client.py +0 -151
- synth_ai/v0/tracing_v1/local.py +0 -18
- synth_ai/v0/tracing_v1/log_client_base.py +0 -73
- synth_ai/v0/tracing_v1/retry_queue.py +0 -186
- synth_ai/v0/tracing_v1/trackers.py +0 -515
- synth_ai/v0/tracing_v1/upload.py +0 -527
- synth_ai/v0/tracing_v1/utils.py +0 -9
- synth_ai/v0/tracing_v3/__init__.py +0 -10
- synth_ai/v0/tracing_v3/abstractions.py +0 -3
- synth_ai/v0/tracing_v3/decorators.py +0 -3
- synth_ai/v0/tracing_v3/llm_call_record_helpers.py +0 -3
- synth_ai/v0/tracing_v3/session_tracer.py +0 -3
- synth_ai-0.2.14.dist-info/METADATA +0 -139
- synth_ai-0.2.14.dist-info/RECORD +0 -762
- synth_ai-0.2.14.dist-info/top_level.txt +0 -2
- /synth_ai/{demos/demo_task_apps → cli/demo_apps}/crafter/__init__.py +0 -0
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/__init__.py +0 -0
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/crafter/configs/crafter_fft_4b.toml +0 -0
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/crafter/configs/rl_from_base_qwen4b.toml +0 -0
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/__init__.py +0 -0
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/_common.py +0 -0
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/app.py +0 -0
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/config.toml +0 -0
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/deploy_modal.py +0 -0
- {examples/task_apps → synth_ai/core/apps}/__init__.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/examples/basic_usage.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/hooks.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/lm_call_record_abstractions.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/replica_sync.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/serialization.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/storage/__init__.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/storage/exceptions.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/storage/types.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/storage/utils.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/turso/__init__.py +0 -0
- /synth_ai/{evals → sdk/judging}/types.py +0 -0
- /synth_ai/{learning → sdk/learning}/algorithms.py +0 -0
- /synth_ai/{learning → sdk/learning}/config.py +0 -0
- /synth_ai/{learning → sdk/learning}/constants.py +0 -0
- /synth_ai/{learning → sdk/learning}/core.py +0 -0
- /synth_ai/{learning → sdk/learning}/gateway.py +0 -0
- /synth_ai/{learning → sdk/learning}/rl/__init__.py +0 -0
- /synth_ai/{learning → sdk/learning}/rl/config.py +0 -0
- /synth_ai/{learning → sdk/learning}/rl_client.py +0 -0
- /synth_ai/{learning → sdk/learning}/sft/__init__.py +0 -0
- /synth_ai/{learning → sdk/learning}/sse.py +0 -0
- /synth_ai/{task → sdk/task}/auth.py +0 -0
- /synth_ai/{task → sdk/task}/client.py +0 -0
- /synth_ai/{task → sdk/task}/errors.py +0 -0
- /synth_ai/{task → sdk/task}/health.py +0 -0
- /synth_ai/{task → sdk/task}/json.py +0 -0
- /synth_ai/{task → sdk/task}/rubrics/models.py +0 -0
- /synth_ai/{task → sdk/task}/rubrics/scoring.py +0 -0
- /synth_ai/{task → sdk/task}/rubrics/strict.py +0 -0
- /synth_ai/{task → sdk/task}/vendors.py +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.4.1.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.4.1.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1190 @@
|
|
|
1
|
+
"""In-process task app support for local development and demos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import signal
|
|
9
|
+
import socket
|
|
10
|
+
import subprocess
|
|
11
|
+
import threading
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Callable, Optional
|
|
14
|
+
from urllib.parse import urlparse
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
import uvicorn
|
|
18
|
+
from uvicorn._types import ASGIApplication
|
|
19
|
+
|
|
20
|
+
from synth_ai.core.apps.common import get_asgi_app, load_module
|
|
21
|
+
from synth_ai.core.integrations.cloudflare import (
|
|
22
|
+
create_tunnel,
|
|
23
|
+
ensure_cloudflared_installed,
|
|
24
|
+
open_managed_tunnel,
|
|
25
|
+
open_quick_tunnel_with_dns_verification,
|
|
26
|
+
rotate_tunnel,
|
|
27
|
+
stop_tunnel,
|
|
28
|
+
wait_for_health_check,
|
|
29
|
+
)
|
|
30
|
+
from synth_ai.core.paths import REPO_ROOT, configure_import_paths
|
|
31
|
+
from synth_ai.sdk.task.server import TaskAppConfig, create_task_app
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
# Global registry for signal handlers
|
|
36
|
+
_registered_instances: set[InProcessTaskApp] = set()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _find_available_port(host: str, start_port: int, max_attempts: int = 100) -> int:
|
|
40
|
+
"""
|
|
41
|
+
Find an available port starting from start_port.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
host: Host to bind to
|
|
45
|
+
start_port: Starting port number
|
|
46
|
+
max_attempts: Maximum number of ports to try
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Available port number
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
RuntimeError: If no available port found
|
|
53
|
+
"""
|
|
54
|
+
for offset in range(max_attempts):
|
|
55
|
+
port = start_port + offset
|
|
56
|
+
try:
|
|
57
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
58
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
59
|
+
sock.bind((host, port))
|
|
60
|
+
return port
|
|
61
|
+
except OSError:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
raise RuntimeError(
|
|
65
|
+
f"Could not find available port starting from {start_port} "
|
|
66
|
+
f"(tried {max_attempts} ports)"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _is_port_available(host: str, port: int) -> bool:
|
|
71
|
+
"""Check if a port is available."""
|
|
72
|
+
try:
|
|
73
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
74
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
75
|
+
sock.bind((host, port))
|
|
76
|
+
return True
|
|
77
|
+
except OSError:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _kill_process_on_port(host: str, port: int) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Attempt to kill any process using the specified port.
|
|
86
|
+
|
|
87
|
+
Note: This is a best-effort operation and may not work on all systems.
|
|
88
|
+
"""
|
|
89
|
+
import subprocess
|
|
90
|
+
import sys
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
if sys.platform == "win32":
|
|
94
|
+
# Windows
|
|
95
|
+
result = subprocess.run(
|
|
96
|
+
["netstat", "-ano"], capture_output=True, text=True, check=False
|
|
97
|
+
)
|
|
98
|
+
for line in result.stdout.splitlines():
|
|
99
|
+
if f":{port}" in line and "LISTENING" in line:
|
|
100
|
+
parts = line.split()
|
|
101
|
+
if len(parts) > 4:
|
|
102
|
+
pid = parts[-1]
|
|
103
|
+
try:
|
|
104
|
+
subprocess.run(
|
|
105
|
+
["taskkill", "/F", "/PID", pid],
|
|
106
|
+
capture_output=True,
|
|
107
|
+
check=False,
|
|
108
|
+
)
|
|
109
|
+
logger.info(f"Killed process {pid} on port {port}")
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
else:
|
|
113
|
+
# Unix-like (macOS, Linux)
|
|
114
|
+
result = subprocess.run(
|
|
115
|
+
["lsof", "-ti", f":{port}"],
|
|
116
|
+
capture_output=True,
|
|
117
|
+
text=True,
|
|
118
|
+
check=False,
|
|
119
|
+
)
|
|
120
|
+
if result.stdout.strip():
|
|
121
|
+
pids = result.stdout.strip().split()
|
|
122
|
+
for pid in pids:
|
|
123
|
+
try:
|
|
124
|
+
subprocess.run(
|
|
125
|
+
["kill", "-9", pid],
|
|
126
|
+
capture_output=True,
|
|
127
|
+
check=False,
|
|
128
|
+
)
|
|
129
|
+
logger.info(f"Killed process {pid} on port {port}")
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.debug(f"Could not kill process on port {port}: {e}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def _resolve_via_public_dns(hostname: str, timeout: float = 5.0) -> Optional[str]:
|
|
137
|
+
"""
|
|
138
|
+
Resolve hostname using public DNS servers (1.1.1.1, 8.8.8.8).
|
|
139
|
+
|
|
140
|
+
This bypasses local DNS caching issues that can cause NXDOMAIN errors
|
|
141
|
+
when the local resolver has stale cached responses.
|
|
142
|
+
|
|
143
|
+
Returns the first resolved IP address, or None if resolution fails.
|
|
144
|
+
"""
|
|
145
|
+
loop = asyncio.get_event_loop()
|
|
146
|
+
|
|
147
|
+
for dns_server in ("1.1.1.1", "8.8.8.8"):
|
|
148
|
+
try:
|
|
149
|
+
result = await loop.run_in_executor(
|
|
150
|
+
None,
|
|
151
|
+
lambda server=dns_server: subprocess.run(
|
|
152
|
+
["dig", f"@{server}", "+short", hostname],
|
|
153
|
+
capture_output=True,
|
|
154
|
+
text=True,
|
|
155
|
+
timeout=timeout,
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
159
|
+
# Return first IP (dig may return multiple)
|
|
160
|
+
first_ip = result.stdout.strip().splitlines()[0].strip()
|
|
161
|
+
if first_ip and not first_ip.endswith("."): # Skip CNAME responses
|
|
162
|
+
logger.debug(f"Resolved {hostname} via {dns_server}: {first_ip}")
|
|
163
|
+
return first_ip
|
|
164
|
+
except FileNotFoundError:
|
|
165
|
+
# dig not available, try socket resolution instead
|
|
166
|
+
try:
|
|
167
|
+
result = await loop.run_in_executor(
|
|
168
|
+
None,
|
|
169
|
+
lambda: socket.gethostbyname(hostname),
|
|
170
|
+
)
|
|
171
|
+
if result:
|
|
172
|
+
logger.debug(f"Resolved {hostname} via system DNS: {result}")
|
|
173
|
+
return result
|
|
174
|
+
except socket.gaierror:
|
|
175
|
+
pass
|
|
176
|
+
except subprocess.TimeoutExpired:
|
|
177
|
+
logger.debug(f"DNS timeout resolving {hostname} via {dns_server}")
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.debug(f"DNS resolution error for {hostname} via {dns_server}: {e}")
|
|
180
|
+
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def _verify_tunnel_ready(
|
|
185
|
+
tunnel_url: str,
|
|
186
|
+
api_key: str,
|
|
187
|
+
*,
|
|
188
|
+
max_retries: int | None = None,
|
|
189
|
+
retry_delay: float | None = None,
|
|
190
|
+
timeout_per_request: float = 10.0,
|
|
191
|
+
verify_tls: bool = True,
|
|
192
|
+
) -> bool:
|
|
193
|
+
"""
|
|
194
|
+
Verify that a Cloudflare tunnel is actually routing traffic (not just DNS-resolvable).
|
|
195
|
+
|
|
196
|
+
A tunnel can resolve via DNS before HTTP routing is ready. This helper polls both
|
|
197
|
+
/health and /task_info until they return 200 or retries are exhausted.
|
|
198
|
+
|
|
199
|
+
IMPORTANT: Uses public DNS (1.1.1.1) to bypass local DNS cache issues.
|
|
200
|
+
Local DNS may have stale NXDOMAIN cached even after tunnel DNS is created.
|
|
201
|
+
|
|
202
|
+
Environment variables for tuning:
|
|
203
|
+
SYNTH_TUNNEL_VERIFY_MAX_RETRIES: Number of retry attempts (default: 30)
|
|
204
|
+
SYNTH_TUNNEL_VERIFY_DELAY_SECS: Delay between retries in seconds (default: 2.0)
|
|
205
|
+
|
|
206
|
+
With defaults, waits up to 60 seconds for tunnel to become ready.
|
|
207
|
+
"""
|
|
208
|
+
# Allow env var overrides for reliability tuning
|
|
209
|
+
if max_retries is None:
|
|
210
|
+
max_retries = int(os.getenv("SYNTH_TUNNEL_VERIFY_MAX_RETRIES", "30"))
|
|
211
|
+
if retry_delay is None:
|
|
212
|
+
retry_delay = float(os.getenv("SYNTH_TUNNEL_VERIFY_DELAY_SECS", "2.0"))
|
|
213
|
+
|
|
214
|
+
# Initial delay before first check - tunnels often need a moment after DNS resolves
|
|
215
|
+
initial_delay = float(os.getenv("SYNTH_TUNNEL_VERIFY_INITIAL_DELAY_SECS", "3.0"))
|
|
216
|
+
if initial_delay > 0:
|
|
217
|
+
logger.debug(f"Waiting {initial_delay}s for tunnel to stabilize before verification...")
|
|
218
|
+
await asyncio.sleep(initial_delay)
|
|
219
|
+
|
|
220
|
+
# Parse hostname from URL
|
|
221
|
+
parsed = urlparse(tunnel_url)
|
|
222
|
+
hostname = parsed.netloc
|
|
223
|
+
scheme = parsed.scheme or "https"
|
|
224
|
+
|
|
225
|
+
headers = {
|
|
226
|
+
"X-API-Key": api_key,
|
|
227
|
+
"Authorization": f"Bearer {api_key}",
|
|
228
|
+
"Host": hostname, # Always set Host header for IP-based requests
|
|
229
|
+
}
|
|
230
|
+
aliases = (os.getenv("ENVIRONMENT_API_KEY_ALIASES") or "").strip()
|
|
231
|
+
if aliases:
|
|
232
|
+
headers["X-API-Keys"] = ",".join(
|
|
233
|
+
[api_key, *[p.strip() for p in aliases.split(",") if p.strip()]]
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
logger.info(f"Verifying tunnel is routing traffic (max {max_retries} attempts, {retry_delay}s delay)...")
|
|
237
|
+
|
|
238
|
+
# Track resolved IP to avoid re-resolving every attempt
|
|
239
|
+
resolved_ip: Optional[str] = None
|
|
240
|
+
|
|
241
|
+
for attempt in range(max_retries):
|
|
242
|
+
try:
|
|
243
|
+
# Try to resolve IP via public DNS if we don't have it yet
|
|
244
|
+
if resolved_ip is None:
|
|
245
|
+
resolved_ip = await _resolve_via_public_dns(hostname)
|
|
246
|
+
if resolved_ip:
|
|
247
|
+
logger.info(f"Resolved tunnel hostname via public DNS: {hostname} -> {resolved_ip}")
|
|
248
|
+
|
|
249
|
+
# If we have a resolved IP, use curl with --resolve for proper SNI handling
|
|
250
|
+
# httpx connecting to an IP directly fails SSL handshake due to SNI issues
|
|
251
|
+
if resolved_ip:
|
|
252
|
+
loop = asyncio.get_event_loop()
|
|
253
|
+
|
|
254
|
+
# Use curl with --resolve to bypass local DNS while maintaining proper SNI
|
|
255
|
+
async def curl_check(path: str) -> int:
|
|
256
|
+
try:
|
|
257
|
+
result = await loop.run_in_executor(
|
|
258
|
+
None,
|
|
259
|
+
lambda: subprocess.run(
|
|
260
|
+
[
|
|
261
|
+
"curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
|
|
262
|
+
"--resolve", f"{hostname}:443:{resolved_ip}",
|
|
263
|
+
"-H", f"X-API-Key: {api_key}",
|
|
264
|
+
"-H", f"Authorization: Bearer {api_key}",
|
|
265
|
+
f"https://{hostname}{path}",
|
|
266
|
+
],
|
|
267
|
+
capture_output=True,
|
|
268
|
+
text=True,
|
|
269
|
+
timeout=timeout_per_request,
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
return int(result.stdout.strip()) if result.returncode == 0 else 0
|
|
273
|
+
except Exception:
|
|
274
|
+
return 0
|
|
275
|
+
|
|
276
|
+
health_status = await curl_check("/health")
|
|
277
|
+
task_info_status = await curl_check("/task_info")
|
|
278
|
+
|
|
279
|
+
if health_status == 200 and task_info_status == 200:
|
|
280
|
+
logger.info(
|
|
281
|
+
f"Tunnel ready after {attempt + 1} attempt(s): "
|
|
282
|
+
f"health={health_status}, task_info={task_info_status}"
|
|
283
|
+
)
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
logger.debug(
|
|
287
|
+
"Tunnel not ready (attempt %s/%s): health=%s task_info=%s",
|
|
288
|
+
attempt + 1,
|
|
289
|
+
max_retries,
|
|
290
|
+
health_status,
|
|
291
|
+
task_info_status,
|
|
292
|
+
)
|
|
293
|
+
else:
|
|
294
|
+
# Fall back to hostname-based request (local DNS)
|
|
295
|
+
base = tunnel_url.rstrip("/")
|
|
296
|
+
async with httpx.AsyncClient(timeout=timeout_per_request, verify=verify_tls) as client:
|
|
297
|
+
health = await client.get(f"{base}/health", headers=headers)
|
|
298
|
+
task_info = await client.get(f"{base}/task_info", headers=headers)
|
|
299
|
+
|
|
300
|
+
if health.status_code == 200 and task_info.status_code == 200:
|
|
301
|
+
logger.info(
|
|
302
|
+
f"Tunnel ready after {attempt + 1} attempt(s): "
|
|
303
|
+
f"health={health.status_code}, task_info={task_info.status_code}"
|
|
304
|
+
)
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
logger.debug(
|
|
308
|
+
"Tunnel not ready (attempt %s/%s): health=%s task_info=%s",
|
|
309
|
+
attempt + 1,
|
|
310
|
+
max_retries,
|
|
311
|
+
health.status_code,
|
|
312
|
+
task_info.status_code,
|
|
313
|
+
)
|
|
314
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
315
|
+
logger.debug(
|
|
316
|
+
"Tunnel readiness check failed (attempt %s/%s): %s",
|
|
317
|
+
attempt + 1,
|
|
318
|
+
max_retries,
|
|
319
|
+
exc,
|
|
320
|
+
)
|
|
321
|
+
# Clear resolved IP on connection errors - might need to re-resolve
|
|
322
|
+
if "connect" in str(exc).lower() or "resolve" in str(exc).lower():
|
|
323
|
+
resolved_ip = None
|
|
324
|
+
|
|
325
|
+
if attempt < max_retries - 1:
|
|
326
|
+
# Log progress periodically (every 5 attempts)
|
|
327
|
+
if (attempt + 1) % 5 == 0:
|
|
328
|
+
elapsed = (attempt + 1) * retry_delay
|
|
329
|
+
remaining = (max_retries - attempt - 1) * retry_delay
|
|
330
|
+
logger.info(
|
|
331
|
+
f"Still waiting for tunnel... ({elapsed:.0f}s elapsed, {remaining:.0f}s remaining)"
|
|
332
|
+
)
|
|
333
|
+
await asyncio.sleep(retry_delay)
|
|
334
|
+
|
|
335
|
+
logger.warning(f"Tunnel verification exhausted after {max_retries} attempts")
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
async def _verify_preconfigured_url_ready(
|
|
340
|
+
tunnel_url: str,
|
|
341
|
+
api_key: str,
|
|
342
|
+
*,
|
|
343
|
+
extra_headers: Optional[dict[str, str]] = None,
|
|
344
|
+
max_retries: int = 10,
|
|
345
|
+
retry_delay: float = 1.0,
|
|
346
|
+
timeout_per_request: float = 10.0,
|
|
347
|
+
) -> bool:
|
|
348
|
+
"""
|
|
349
|
+
Verify that a preconfigured tunnel URL is routing traffic.
|
|
350
|
+
|
|
351
|
+
This is similar to _verify_tunnel_ready but designed for external tunnel
|
|
352
|
+
providers (ngrok, etc.) where we don't control the tunnel setup.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
tunnel_url: The external tunnel URL to verify
|
|
356
|
+
api_key: API key for task app authentication
|
|
357
|
+
extra_headers: Additional headers for the tunnel (e.g., auth tokens)
|
|
358
|
+
max_retries: Number of retry attempts
|
|
359
|
+
retry_delay: Delay between retries in seconds
|
|
360
|
+
timeout_per_request: Timeout for each HTTP request
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
True if tunnel is accessible, False otherwise
|
|
364
|
+
"""
|
|
365
|
+
base = tunnel_url.rstrip("/")
|
|
366
|
+
headers = {
|
|
367
|
+
"X-API-Key": api_key,
|
|
368
|
+
"Authorization": f"Bearer {api_key}",
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
# Add any extra headers (e.g., custom auth tokens)
|
|
372
|
+
if extra_headers:
|
|
373
|
+
headers.update(extra_headers)
|
|
374
|
+
|
|
375
|
+
logger.info(f"Verifying preconfigured URL is accessible (max {max_retries} attempts)...")
|
|
376
|
+
|
|
377
|
+
for attempt in range(max_retries):
|
|
378
|
+
try:
|
|
379
|
+
async with httpx.AsyncClient(timeout=timeout_per_request) as client:
|
|
380
|
+
health = await client.get(f"{base}/health", headers=headers)
|
|
381
|
+
|
|
382
|
+
# Only accept 200 for health checks - other codes may indicate misrouting
|
|
383
|
+
if health.status_code == 200:
|
|
384
|
+
logger.info(
|
|
385
|
+
f"Preconfigured URL ready after {attempt + 1} attempt(s): "
|
|
386
|
+
f"health={health.status_code}"
|
|
387
|
+
)
|
|
388
|
+
return True
|
|
389
|
+
|
|
390
|
+
logger.debug(
|
|
391
|
+
"Preconfigured URL not ready (attempt %s/%s): health=%s",
|
|
392
|
+
attempt + 1,
|
|
393
|
+
max_retries,
|
|
394
|
+
health.status_code,
|
|
395
|
+
)
|
|
396
|
+
except Exception as exc:
|
|
397
|
+
logger.debug(
|
|
398
|
+
"Preconfigured URL check failed (attempt %s/%s): %s",
|
|
399
|
+
attempt + 1,
|
|
400
|
+
max_retries,
|
|
401
|
+
exc,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
if attempt < max_retries - 1:
|
|
405
|
+
await asyncio.sleep(retry_delay)
|
|
406
|
+
|
|
407
|
+
logger.warning(f"Preconfigured URL verification exhausted after {max_retries} attempts")
|
|
408
|
+
return False
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class InProcessTaskApp:
|
|
412
|
+
"""
|
|
413
|
+
Context manager for running task apps in-process with automatic tunneling.
|
|
414
|
+
|
|
415
|
+
This class simplifies local development and demos by:
|
|
416
|
+
1. Starting a task app server in a background thread
|
|
417
|
+
2. Opening a tunnel automatically (Cloudflare by default, or use preconfigured URL)
|
|
418
|
+
3. Providing the tunnel URL for GEPA/MIPRO jobs
|
|
419
|
+
4. Cleaning up everything on exit
|
|
420
|
+
|
|
421
|
+
Supports multiple input methods:
|
|
422
|
+
- FastAPI app instance (most direct)
|
|
423
|
+
- TaskAppConfig object
|
|
424
|
+
- Config factory function (Callable[[], TaskAppConfig])
|
|
425
|
+
- Task app file path (fallback for compatibility)
|
|
426
|
+
|
|
427
|
+
Tunnel modes:
|
|
428
|
+
- "quick": Cloudflare quick tunnel (default for local dev)
|
|
429
|
+
- "named": Cloudflare named/managed tunnel
|
|
430
|
+
- "local": No tunnel, use localhost URL directly
|
|
431
|
+
- "preconfigured": Use externally-provided URL (set via preconfigured_url param or
|
|
432
|
+
SYNTH_TASK_APP_URL env var). Useful for ngrok or other external tunnel providers.
|
|
433
|
+
|
|
434
|
+
Example:
|
|
435
|
+
```python
|
|
436
|
+
from synth_ai.sdk.task.in_process import InProcessTaskApp
|
|
437
|
+
from heartdisease_task_app import build_config
|
|
438
|
+
|
|
439
|
+
# Default: use Cloudflare quick tunnel
|
|
440
|
+
async with InProcessTaskApp(
|
|
441
|
+
config_factory=build_config,
|
|
442
|
+
port=8114,
|
|
443
|
+
) as task_app:
|
|
444
|
+
print(f"Task app running at: {task_app.url}")
|
|
445
|
+
|
|
446
|
+
# Use preconfigured URL (e.g., from ngrok, localtunnel, etc.)
|
|
447
|
+
async with InProcessTaskApp(
|
|
448
|
+
config_factory=build_config,
|
|
449
|
+
port=8000,
|
|
450
|
+
tunnel_mode="preconfigured",
|
|
451
|
+
preconfigured_url="https://abc123.ngrok.io",
|
|
452
|
+
) as task_app:
|
|
453
|
+
print(f"Task app running at: {task_app.url}")
|
|
454
|
+
```
|
|
455
|
+
"""
|
|
456
|
+
|
|
457
|
+
def __init__(
|
|
458
|
+
self,
|
|
459
|
+
*,
|
|
460
|
+
app: Optional[ASGIApplication] = None,
|
|
461
|
+
config: Optional[TaskAppConfig] = None,
|
|
462
|
+
config_factory: Optional[Callable[[], TaskAppConfig]] = None,
|
|
463
|
+
task_app_path: Optional[Path | str] = None,
|
|
464
|
+
port: int = 8114,
|
|
465
|
+
host: str = "127.0.0.1",
|
|
466
|
+
tunnel_mode: str = "quick",
|
|
467
|
+
preconfigured_url: Optional[str] = None,
|
|
468
|
+
preconfigured_auth_header: Optional[str] = None,
|
|
469
|
+
preconfigured_auth_token: Optional[str] = None,
|
|
470
|
+
api_key: Optional[str] = None,
|
|
471
|
+
health_check_timeout: float = 30.0,
|
|
472
|
+
auto_find_port: bool = True,
|
|
473
|
+
skip_tunnel_verification: bool = True, # Default True - verification is unreliable
|
|
474
|
+
force_new_tunnel: bool = False,
|
|
475
|
+
on_start: Optional[Callable[[InProcessTaskApp], None]] = None,
|
|
476
|
+
on_stop: Optional[Callable[[InProcessTaskApp], None]] = None,
|
|
477
|
+
):
|
|
478
|
+
"""
|
|
479
|
+
Initialize in-process task app.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
app: FastAPI app instance (most direct)
|
|
483
|
+
config: TaskAppConfig object
|
|
484
|
+
config_factory: Callable that returns TaskAppConfig
|
|
485
|
+
task_app_path: Path to task app .py file (fallback)
|
|
486
|
+
port: Local port to run server on
|
|
487
|
+
host: Host to bind to (default: 127.0.0.1, use 0.0.0.0 for external access)
|
|
488
|
+
tunnel_mode: Tunnel mode - "quick", "named", "local", or "preconfigured"
|
|
489
|
+
preconfigured_url: External tunnel URL to use when tunnel_mode="preconfigured".
|
|
490
|
+
Can also be set via SYNTH_TASK_APP_URL env var.
|
|
491
|
+
preconfigured_auth_header: Optional auth header name for preconfigured URL
|
|
492
|
+
(e.g., "x-custom-auth-token")
|
|
493
|
+
preconfigured_auth_token: Optional auth token value for preconfigured URL
|
|
494
|
+
api_key: API key for health checks (defaults to ENVIRONMENT_API_KEY env var)
|
|
495
|
+
health_check_timeout: Max time to wait for health check in seconds
|
|
496
|
+
auto_find_port: If True, automatically find available port if requested port is busy
|
|
497
|
+
skip_tunnel_verification: If True, skip HTTP verification of tunnel connectivity.
|
|
498
|
+
Useful when the tunnel URL is known to be valid.
|
|
499
|
+
force_new_tunnel: If True, create a fresh tunnel instead of reusing existing one.
|
|
500
|
+
Use this when an existing managed tunnel is stale/broken.
|
|
501
|
+
on_start: Optional callback called when task app starts (receives self)
|
|
502
|
+
on_stop: Optional callback called when task app stops (receives self)
|
|
503
|
+
|
|
504
|
+
Raises:
|
|
505
|
+
ValueError: If multiple or no input methods provided, or invalid parameters
|
|
506
|
+
FileNotFoundError: If task_app_path doesn't exist
|
|
507
|
+
"""
|
|
508
|
+
# Validate: exactly one input method
|
|
509
|
+
inputs = [app, config, config_factory, task_app_path]
|
|
510
|
+
if sum(x is not None for x in inputs) != 1:
|
|
511
|
+
raise ValueError(
|
|
512
|
+
"Must provide exactly one of: app, config, config_factory, or task_app_path"
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Validate port range
|
|
516
|
+
if not (1024 <= port <= 65535):
|
|
517
|
+
raise ValueError(f"Port must be in range [1024, 65535], got {port}")
|
|
518
|
+
|
|
519
|
+
# Validate host (allow 0.0.0.0 for container environments)
|
|
520
|
+
allowed_hosts = ("127.0.0.1", "localhost", "0.0.0.0")
|
|
521
|
+
if host not in allowed_hosts:
|
|
522
|
+
raise ValueError(
|
|
523
|
+
f"Host must be one of {allowed_hosts} for security reasons, got {host}"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# Validate tunnel_mode
|
|
527
|
+
valid_modes = ("local", "quick", "named", "preconfigured")
|
|
528
|
+
if tunnel_mode not in valid_modes:
|
|
529
|
+
raise ValueError(f"tunnel_mode must be one of {valid_modes}, got {tunnel_mode}")
|
|
530
|
+
|
|
531
|
+
# Validate task_app_path if provided
|
|
532
|
+
if task_app_path:
|
|
533
|
+
path = Path(task_app_path)
|
|
534
|
+
if not path.exists():
|
|
535
|
+
raise FileNotFoundError(f"Task app path does not exist: {task_app_path}")
|
|
536
|
+
if path.suffix != ".py":
|
|
537
|
+
raise ValueError(
|
|
538
|
+
f"Task app path must be a .py file, got {task_app_path}"
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
self._app_input = app
|
|
542
|
+
self._config = config
|
|
543
|
+
self._config_factory = config_factory
|
|
544
|
+
self._task_app_path = Path(task_app_path) if task_app_path else None
|
|
545
|
+
|
|
546
|
+
self.port = port
|
|
547
|
+
self.host = host
|
|
548
|
+
self.tunnel_mode = tunnel_mode
|
|
549
|
+
self.preconfigured_url = preconfigured_url
|
|
550
|
+
self.preconfigured_auth_header = preconfigured_auth_header
|
|
551
|
+
self.preconfigured_auth_token = preconfigured_auth_token
|
|
552
|
+
self.api_key = api_key
|
|
553
|
+
self.health_check_timeout = health_check_timeout
|
|
554
|
+
self.auto_find_port = auto_find_port
|
|
555
|
+
self.skip_tunnel_verification = skip_tunnel_verification
|
|
556
|
+
self.force_new_tunnel = force_new_tunnel
|
|
557
|
+
self.on_start = on_start
|
|
558
|
+
self.on_stop = on_stop
|
|
559
|
+
|
|
560
|
+
self.url: Optional[str] = None
|
|
561
|
+
self._tunnel_proc: Optional[Any] = None
|
|
562
|
+
self._app: Optional[ASGIApplication] = None
|
|
563
|
+
self._uvicorn_server: Optional[uvicorn.Server] = None
|
|
564
|
+
self._server_thread: Optional[Any] = None
|
|
565
|
+
self._original_port = port # Track original requested port
|
|
566
|
+
self._is_preconfigured = False # Track if using preconfigured URL
|
|
567
|
+
self._dns_verified_by_backend = False # Track if backend verified DNS propagation
|
|
568
|
+
|
|
569
|
+
async def __aenter__(self) -> InProcessTaskApp:
|
|
570
|
+
"""Start task app and tunnel."""
|
|
571
|
+
|
|
572
|
+
# For named tunnels, pre-fetch tunnel config to get the correct port
|
|
573
|
+
# (existing tunnels are configured for a specific port)
|
|
574
|
+
mode = os.getenv("SYNTH_TUNNEL_MODE", self.tunnel_mode)
|
|
575
|
+
if mode == "named":
|
|
576
|
+
try:
|
|
577
|
+
from synth_ai.core.env import get_api_key as get_synth_api_key
|
|
578
|
+
synth_api_key = get_synth_api_key()
|
|
579
|
+
if synth_api_key is None:
|
|
580
|
+
raise ValueError("SYNTH_API_KEY is required for named tunnel mode")
|
|
581
|
+
tunnel_config = await self._fetch_tunnel_config(synth_api_key)
|
|
582
|
+
tunnel_port = tunnel_config.get("local_port")
|
|
583
|
+
if tunnel_config.get("hostname") and tunnel_port and tunnel_port != self.port:
|
|
584
|
+
logger.info(
|
|
585
|
+
f"Existing managed tunnel is configured for port {tunnel_port}, "
|
|
586
|
+
f"adjusting from requested port {self.port}"
|
|
587
|
+
)
|
|
588
|
+
self.port = tunnel_port
|
|
589
|
+
# Store config for later use to avoid re-fetching
|
|
590
|
+
self._prefetched_tunnel_config = tunnel_config
|
|
591
|
+
except Exception as e:
|
|
592
|
+
logger.debug(f"Pre-fetch tunnel config failed: {e}")
|
|
593
|
+
self._prefetched_tunnel_config = None
|
|
594
|
+
else:
|
|
595
|
+
self._prefetched_tunnel_config = None
|
|
596
|
+
|
|
597
|
+
logger.info(f"Starting in-process task app on {self.host}:{self.port}")
|
|
598
|
+
|
|
599
|
+
# For named tunnels, the port is baked into the tunnel config - we MUST use it
|
|
600
|
+
tunnel_config = getattr(self, "_prefetched_tunnel_config", None) or {}
|
|
601
|
+
tunnel_port = tunnel_config.get("local_port")
|
|
602
|
+
is_named_tunnel_port = mode == "named" and tunnel_port and tunnel_port == self.port
|
|
603
|
+
|
|
604
|
+
# Handle port conflicts
|
|
605
|
+
if not _is_port_available(self.host, self.port):
|
|
606
|
+
if is_named_tunnel_port:
|
|
607
|
+
# Named tunnel port is REQUIRED - kill whatever is using it
|
|
608
|
+
print(f"[CLOUDFLARE-FIX] Named tunnel requires port {self.port}, killing existing process...")
|
|
609
|
+
logger.warning(
|
|
610
|
+
f"Named tunnel is configured for port {self.port}, killing existing process..."
|
|
611
|
+
)
|
|
612
|
+
_kill_process_on_port(self.host, self.port)
|
|
613
|
+
await asyncio.sleep(1.0) # Wait for port to free
|
|
614
|
+
|
|
615
|
+
if not _is_port_available(self.host, self.port):
|
|
616
|
+
raise RuntimeError(
|
|
617
|
+
f"Named tunnel requires port {self.port} but it's still in use after kill attempt. "
|
|
618
|
+
"Manually kill the process using this port, or delete and recreate the tunnel."
|
|
619
|
+
)
|
|
620
|
+
print(f"[CLOUDFLARE-FIX] Port {self.port} freed successfully")
|
|
621
|
+
elif self.auto_find_port:
|
|
622
|
+
print(f"Port {self.port} is in use, attempting to find available port...")
|
|
623
|
+
logger.warning(
|
|
624
|
+
f"Port {self.port} is in use, attempting to find available port..."
|
|
625
|
+
)
|
|
626
|
+
self.port = _find_available_port(self.host, self.port)
|
|
627
|
+
logger.info(f"Using port {self.port} instead")
|
|
628
|
+
else:
|
|
629
|
+
# Try to kill process on port
|
|
630
|
+
logger.warning(
|
|
631
|
+
f"Port {self.port} is in use, attempting to free it..."
|
|
632
|
+
)
|
|
633
|
+
_kill_process_on_port(self.host, self.port)
|
|
634
|
+
await asyncio.sleep(0.5) # Brief wait for port to free
|
|
635
|
+
|
|
636
|
+
if not _is_port_available(self.host, self.port):
|
|
637
|
+
raise RuntimeError(
|
|
638
|
+
f"Port {self.port} is still in use. "
|
|
639
|
+
"Set auto_find_port=True to automatically find an available port."
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# 1. Get FastAPI app from whichever input method was provided
|
|
643
|
+
if self._app_input:
|
|
644
|
+
# Direct app - use as-is
|
|
645
|
+
self._app = self._app_input
|
|
646
|
+
|
|
647
|
+
elif self._config:
|
|
648
|
+
# TaskAppConfig - create app from it
|
|
649
|
+
self._app = create_task_app(self._config) # type: ignore[assignment]
|
|
650
|
+
|
|
651
|
+
elif self._config_factory:
|
|
652
|
+
# Callable - call it to get config, then create app
|
|
653
|
+
config = self._config_factory()
|
|
654
|
+
self._app = create_task_app(config) # type: ignore[assignment]
|
|
655
|
+
|
|
656
|
+
elif self._task_app_path:
|
|
657
|
+
# File path - load module and extract app
|
|
658
|
+
configure_import_paths(self._task_app_path, REPO_ROOT)
|
|
659
|
+
module = load_module(
|
|
660
|
+
self._task_app_path,
|
|
661
|
+
f"_inprocess_{self._task_app_path.stem}_{id(self)}",
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
# Try to get app directly first
|
|
665
|
+
try:
|
|
666
|
+
self._app = get_asgi_app(module) # type: ignore[assignment]
|
|
667
|
+
except RuntimeError:
|
|
668
|
+
# If no app found, try to get build_config function
|
|
669
|
+
build_config = getattr(module, "build_config", None)
|
|
670
|
+
if build_config and callable(build_config):
|
|
671
|
+
config = build_config()
|
|
672
|
+
self._app = create_task_app(config) # type: ignore[assignment]
|
|
673
|
+
else:
|
|
674
|
+
# Try registry lookup as last resort
|
|
675
|
+
from synth_ai.sdk.task.apps import registry
|
|
676
|
+
app_id = getattr(module, "APP_ID", None) or self._task_app_path.stem
|
|
677
|
+
entry = registry.get(app_id)
|
|
678
|
+
if entry and entry.config_factory:
|
|
679
|
+
config = entry.config_factory()
|
|
680
|
+
self._app = create_task_app(config) # type: ignore[assignment]
|
|
681
|
+
else:
|
|
682
|
+
raise RuntimeError(
|
|
683
|
+
f"Task app at {self._task_app_path} must expose either:\n"
|
|
684
|
+
f" - An ASGI app via `app = FastAPI(...)` or factory function\n"
|
|
685
|
+
f" - A `build_config()` function that returns TaskAppConfig\n"
|
|
686
|
+
f" - Be registered with register_task_app()"
|
|
687
|
+
) from None
|
|
688
|
+
|
|
689
|
+
# 2. Start uvicorn in background thread
|
|
690
|
+
# Use daemon=True for local testing to allow quick exit
|
|
691
|
+
# The thread will be killed when the process exits
|
|
692
|
+
logger.debug(f"Starting uvicorn server on {self.host}:{self.port}")
|
|
693
|
+
|
|
694
|
+
config = uvicorn.Config(
|
|
695
|
+
self._app, # type: ignore[arg-type]
|
|
696
|
+
host=self.host,
|
|
697
|
+
port=self.port,
|
|
698
|
+
reload=False,
|
|
699
|
+
log_level="info",
|
|
700
|
+
)
|
|
701
|
+
self._uvicorn_server = uvicorn.Server(config)
|
|
702
|
+
|
|
703
|
+
def serve():
|
|
704
|
+
try:
|
|
705
|
+
self._uvicorn_server.run() # type: ignore[attr-defined]
|
|
706
|
+
except Exception as exc:
|
|
707
|
+
logger.debug(f"Uvicorn server stopped: {exc}")
|
|
708
|
+
|
|
709
|
+
self._server_thread = threading.Thread(
|
|
710
|
+
target=serve,
|
|
711
|
+
name=f"synth-uvicorn-{self.port}",
|
|
712
|
+
daemon=True, # Daemon thread dies when main process exits
|
|
713
|
+
)
|
|
714
|
+
self._server_thread.start()
|
|
715
|
+
|
|
716
|
+
# 3. Wait for health check
|
|
717
|
+
api_key = self.api_key or self._get_api_key()
|
|
718
|
+
logger.debug(f"Waiting for health check on {self.host}:{self.port}")
|
|
719
|
+
await wait_for_health_check(
|
|
720
|
+
self.host, self.port, api_key, timeout=self.health_check_timeout
|
|
721
|
+
)
|
|
722
|
+
logger.info(f"Health check passed for {self.host}:{self.port}")
|
|
723
|
+
|
|
724
|
+
# 4. Determine tunnel mode (env var can override)
|
|
725
|
+
mode = os.getenv("SYNTH_TUNNEL_MODE", self.tunnel_mode)
|
|
726
|
+
|
|
727
|
+
# Check for preconfigured URL via env var
|
|
728
|
+
env_preconfigured_url = os.getenv("SYNTH_TASK_APP_URL")
|
|
729
|
+
if env_preconfigured_url:
|
|
730
|
+
mode = "preconfigured"
|
|
731
|
+
self.preconfigured_url = env_preconfigured_url
|
|
732
|
+
logger.info(f"Using preconfigured URL from SYNTH_TASK_APP_URL: {env_preconfigured_url}")
|
|
733
|
+
|
|
734
|
+
override_host = os.getenv("SYNTH_TUNNEL_HOSTNAME")
|
|
735
|
+
|
|
736
|
+
if mode == "preconfigured":
|
|
737
|
+
# Preconfigured mode: use externally-provided URL (e.g., ngrok, localtunnel)
|
|
738
|
+
# This bypasses Cloudflare entirely - the caller is responsible for the tunnel
|
|
739
|
+
if not self.preconfigured_url:
|
|
740
|
+
raise ValueError(
|
|
741
|
+
"tunnel_mode='preconfigured' requires preconfigured_url parameter "
|
|
742
|
+
"or SYNTH_TASK_APP_URL environment variable"
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
self.url = self.preconfigured_url.rstrip("/")
|
|
746
|
+
self._tunnel_proc = None
|
|
747
|
+
self._is_preconfigured = True
|
|
748
|
+
logger.info(f"Using preconfigured tunnel URL: {self.url}")
|
|
749
|
+
|
|
750
|
+
# Optionally verify the preconfigured URL is accessible
|
|
751
|
+
if not self.skip_tunnel_verification:
|
|
752
|
+
api_key = self.api_key or self._get_api_key()
|
|
753
|
+
|
|
754
|
+
# Build headers including any custom auth for the tunnel
|
|
755
|
+
extra_headers: dict[str, str] = {}
|
|
756
|
+
if self.preconfigured_auth_header and self.preconfigured_auth_token:
|
|
757
|
+
extra_headers[self.preconfigured_auth_header] = self.preconfigured_auth_token
|
|
758
|
+
|
|
759
|
+
ready = await _verify_preconfigured_url_ready(
|
|
760
|
+
self.url,
|
|
761
|
+
api_key,
|
|
762
|
+
extra_headers=extra_headers,
|
|
763
|
+
max_retries=10, # Fewer retries - external URL should work quickly
|
|
764
|
+
retry_delay=1.0,
|
|
765
|
+
)
|
|
766
|
+
if ready:
|
|
767
|
+
logger.info(f"Preconfigured URL verified and ready: {self.url}")
|
|
768
|
+
else:
|
|
769
|
+
logger.warning(
|
|
770
|
+
f"Preconfigured URL {self.url} may not be accessible. "
|
|
771
|
+
"Proceeding anyway - set skip_tunnel_verification=True to suppress this warning."
|
|
772
|
+
)
|
|
773
|
+
elif mode == "local":
|
|
774
|
+
# Local mode: skip tunnel, use localhost
|
|
775
|
+
self.url = f"http://{self.host}:{self.port}"
|
|
776
|
+
self._tunnel_proc = None
|
|
777
|
+
logger.info(f"Using local mode: {self.url}")
|
|
778
|
+
elif mode == "named":
|
|
779
|
+
# Named tunnel mode: fully automatic managed tunnel
|
|
780
|
+
# 1. Check for existing tunnel
|
|
781
|
+
# 2. Auto-create if none exists
|
|
782
|
+
# 3. Auto-start cloudflared if not accessible
|
|
783
|
+
# 4. Verify tunnel is working
|
|
784
|
+
ensure_cloudflared_installed()
|
|
785
|
+
|
|
786
|
+
# For tunnel config, we need the SYNTH_API_KEY (not ENVIRONMENT_API_KEY)
|
|
787
|
+
from synth_ai.core.env import get_api_key as get_synth_api_key
|
|
788
|
+
synth_api_key = get_synth_api_key()
|
|
789
|
+
if synth_api_key is None:
|
|
790
|
+
raise ValueError("SYNTH_API_KEY is required for named tunnel mode")
|
|
791
|
+
|
|
792
|
+
# For task app auth, use the environment API key
|
|
793
|
+
api_key = self.api_key or self._get_api_key()
|
|
794
|
+
|
|
795
|
+
# Use pre-fetched config (port was already adjusted before server started)
|
|
796
|
+
tunnel_config = getattr(self, "_prefetched_tunnel_config", None) or {}
|
|
797
|
+
if not tunnel_config:
|
|
798
|
+
# Fetch if not pre-fetched (shouldn't happen normally)
|
|
799
|
+
tunnel_config = await self._fetch_tunnel_config(synth_api_key)
|
|
800
|
+
|
|
801
|
+
named_host = tunnel_config.get("hostname")
|
|
802
|
+
tunnel_token = tunnel_config.get("tunnel_token")
|
|
803
|
+
|
|
804
|
+
# Track if backend verified DNS (so we can skip local verification)
|
|
805
|
+
dns_verified_by_backend = False
|
|
806
|
+
|
|
807
|
+
# Force ROTATE tunnel if requested (deletes old + creates new, stays within limits)
|
|
808
|
+
if self.force_new_tunnel:
|
|
809
|
+
print("[CLOUDFLARE-FIX] force_new_tunnel=True, rotating tunnel...")
|
|
810
|
+
logger.info("force_new_tunnel=True, rotating tunnel (delete+create)")
|
|
811
|
+
try:
|
|
812
|
+
rotated = await rotate_tunnel(
|
|
813
|
+
synth_api_key=synth_api_key,
|
|
814
|
+
port=self.port,
|
|
815
|
+
reason="force_new_tunnel=True",
|
|
816
|
+
)
|
|
817
|
+
named_host = rotated.get("hostname")
|
|
818
|
+
tunnel_token = rotated.get("tunnel_token")
|
|
819
|
+
dns_verified_by_backend = rotated.get("dns_verified", False)
|
|
820
|
+
print(f"[CLOUDFLARE-FIX] Rotated to fresh tunnel: {named_host}")
|
|
821
|
+
print(f"[CLOUDFLARE-FIX] DNS verified by backend: {dns_verified_by_backend}")
|
|
822
|
+
logger.info(f"Rotated to fresh managed tunnel: {named_host}, dns_verified={dns_verified_by_backend}")
|
|
823
|
+
except Exception as e:
|
|
824
|
+
print(f"[CLOUDFLARE-FIX] Rotation failed: {e}, using existing tunnel: {named_host}")
|
|
825
|
+
logger.warning(f"Rotation failed: {e}, falling back to existing tunnel: {named_host}")
|
|
826
|
+
if not named_host or not tunnel_token:
|
|
827
|
+
raise RuntimeError(
|
|
828
|
+
f"Tunnel rotation failed and no existing tunnel found: {e}\n"
|
|
829
|
+
"Try using tunnel_mode='quick' instead."
|
|
830
|
+
) from e
|
|
831
|
+
# Auto-create tunnel if none exists
|
|
832
|
+
elif not named_host:
|
|
833
|
+
logger.info("No managed tunnel found, creating one automatically...")
|
|
834
|
+
try:
|
|
835
|
+
# Generate subdomain from port or use default
|
|
836
|
+
subdomain = f"task-app-{self.port}"
|
|
837
|
+
new_tunnel = await create_tunnel(
|
|
838
|
+
synth_api_key=synth_api_key,
|
|
839
|
+
port=self.port,
|
|
840
|
+
subdomain=subdomain,
|
|
841
|
+
)
|
|
842
|
+
named_host = new_tunnel.get("hostname")
|
|
843
|
+
tunnel_token = new_tunnel.get("tunnel_token")
|
|
844
|
+
dns_verified_by_backend = new_tunnel.get("dns_verified", False)
|
|
845
|
+
logger.info(f"Created managed tunnel: {named_host}, dns_verified={dns_verified_by_backend}")
|
|
846
|
+
except Exception as e:
|
|
847
|
+
# If tunnel creation fails, suggest using quick tunnels
|
|
848
|
+
raise RuntimeError(
|
|
849
|
+
f"Failed to create managed tunnel: {e}\n"
|
|
850
|
+
"This may be because the backend doesn't have Cloudflare configured.\n"
|
|
851
|
+
"Options:\n"
|
|
852
|
+
" 1. Use tunnel_mode='quick' for automatic quick tunnels\n"
|
|
853
|
+
" 2. Ask your admin to configure Cloudflare credentials on the backend"
|
|
854
|
+
) from e
|
|
855
|
+
|
|
856
|
+
if not named_host or not tunnel_token:
|
|
857
|
+
raise RuntimeError(
|
|
858
|
+
"Tunnel configuration incomplete (missing hostname or token). "
|
|
859
|
+
"Try deleting and recreating the tunnel, or use tunnel_mode='quick'."
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
self.url = f"https://{named_host}"
|
|
863
|
+
# Store dns_verified for use by job (to skip health check)
|
|
864
|
+
self._dns_verified_by_backend = dns_verified_by_backend
|
|
865
|
+
|
|
866
|
+
print(f"[CLOUDFLARE] Named tunnel URL: {self.url}")
|
|
867
|
+
|
|
868
|
+
# CRITICAL: For Cloudflare managed tunnels, DNS will NOT resolve until cloudflared connects.
|
|
869
|
+
# The DNS record exists in Cloudflare, but proxied CNAMEs to .cfargotunnel.com only
|
|
870
|
+
# resolve when the tunnel has an active cloudflared connection.
|
|
871
|
+
# Therefore, we MUST start cloudflared FIRST, then verify the tunnel works.
|
|
872
|
+
|
|
873
|
+
# First, check if cloudflared is already running (tunnel might be accessible)
|
|
874
|
+
ready = await _verify_tunnel_ready(
|
|
875
|
+
self.url,
|
|
876
|
+
api_key,
|
|
877
|
+
max_retries=1, # Single quick check
|
|
878
|
+
retry_delay=0.5,
|
|
879
|
+
verify_tls=_should_verify_tls(),
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
if ready:
|
|
883
|
+
# Tunnel already accessible - cloudflared must be running elsewhere
|
|
884
|
+
self._tunnel_proc = None
|
|
885
|
+
print(f"[CLOUDFLARE] Tunnel already accessible (cloudflared running externally)")
|
|
886
|
+
logger.info(f"Tunnel {self.url} is already accessible (cloudflared running externally)")
|
|
887
|
+
else:
|
|
888
|
+
# Tunnel not accessible - start cloudflared FIRST, then verify
|
|
889
|
+
print(f"[CLOUDFLARE] Starting cloudflared (DNS requires active tunnel connection)...")
|
|
890
|
+
logger.info(f"Starting cloudflared for {self.url}...")
|
|
891
|
+
try:
|
|
892
|
+
self._tunnel_proc = open_managed_tunnel(tunnel_token)
|
|
893
|
+
print(f"[CLOUDFLARE] cloudflared started, PID={self._tunnel_proc.pid}")
|
|
894
|
+
logger.info(f"Started cloudflared (PID: {self._tunnel_proc.pid})")
|
|
895
|
+
except Exception as e:
|
|
896
|
+
print(f"[CLOUDFLARE] ERROR starting cloudflared: {e}")
|
|
897
|
+
raise RuntimeError(
|
|
898
|
+
f"Failed to start cloudflared: {e}\n"
|
|
899
|
+
"Make sure cloudflared is installed: brew install cloudflare/cloudflare/cloudflared"
|
|
900
|
+
) from e
|
|
901
|
+
|
|
902
|
+
# Wait for cloudflared to connect and tunnel to become accessible
|
|
903
|
+
print(f"[CLOUDFLARE] Waiting for tunnel to become accessible...")
|
|
904
|
+
ready = await _verify_tunnel_ready(
|
|
905
|
+
self.url,
|
|
906
|
+
api_key,
|
|
907
|
+
max_retries=15, # Up to ~30 seconds for tunnel to connect
|
|
908
|
+
retry_delay=2.0,
|
|
909
|
+
verify_tls=_should_verify_tls(),
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
if not ready:
|
|
913
|
+
# Tunnel still not accessible after starting cloudflared
|
|
914
|
+
# Clean up and try auto-rotation
|
|
915
|
+
if self._tunnel_proc:
|
|
916
|
+
stop_tunnel(self._tunnel_proc)
|
|
917
|
+
self._tunnel_proc = None
|
|
918
|
+
|
|
919
|
+
print(f"[CLOUDFLARE] Tunnel {self.url} not accessible, attempting rotation...")
|
|
920
|
+
logger.warning(f"Tunnel {self.url} failed to connect. Attempting rotation...")
|
|
921
|
+
|
|
922
|
+
try:
|
|
923
|
+
rotated = await rotate_tunnel(
|
|
924
|
+
synth_api_key=synth_api_key,
|
|
925
|
+
port=self.port,
|
|
926
|
+
reason=f"Tunnel {named_host} failed to connect",
|
|
927
|
+
)
|
|
928
|
+
named_host = rotated.get("hostname")
|
|
929
|
+
tunnel_token = rotated.get("tunnel_token")
|
|
930
|
+
|
|
931
|
+
if not named_host or not tunnel_token:
|
|
932
|
+
raise RuntimeError("Rotation returned incomplete tunnel config")
|
|
933
|
+
|
|
934
|
+
self.url = f"https://{named_host}"
|
|
935
|
+
print(f"[CLOUDFLARE] Rotated to new tunnel: {self.url}")
|
|
936
|
+
|
|
937
|
+
# Start cloudflared with the new token
|
|
938
|
+
self._tunnel_proc = open_managed_tunnel(tunnel_token)
|
|
939
|
+
print(f"[CLOUDFLARE] Started cloudflared for rotated tunnel, PID={self._tunnel_proc.pid}")
|
|
940
|
+
|
|
941
|
+
# Verify the new tunnel
|
|
942
|
+
ready = await _verify_tunnel_ready(
|
|
943
|
+
self.url,
|
|
944
|
+
api_key,
|
|
945
|
+
max_retries=15,
|
|
946
|
+
retry_delay=2.0,
|
|
947
|
+
verify_tls=_should_verify_tls(),
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
if not ready:
|
|
951
|
+
if self._tunnel_proc:
|
|
952
|
+
stop_tunnel(self._tunnel_proc)
|
|
953
|
+
self._tunnel_proc = None
|
|
954
|
+
raise RuntimeError(
|
|
955
|
+
f"Rotated tunnel {self.url} also failed. "
|
|
956
|
+
"Try using tunnel_mode='quick' instead."
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
print(f"[CLOUDFLARE] Rotated tunnel ready: {self.url}")
|
|
960
|
+
|
|
961
|
+
except Exception as rotate_err:
|
|
962
|
+
raise RuntimeError(
|
|
963
|
+
f"Tunnel failed and rotation failed: {rotate_err}\n"
|
|
964
|
+
"Try using tunnel_mode='quick' instead."
|
|
965
|
+
) from rotate_err
|
|
966
|
+
else:
|
|
967
|
+
print(f"[CLOUDFLARE] Tunnel connected and ready: {self.url}")
|
|
968
|
+
|
|
969
|
+
logger.info(f"Using managed tunnel: {self.url}")
|
|
970
|
+
elif mode == "quick":
|
|
971
|
+
# Quick tunnel mode: create tunnel with DNS verification and retry
|
|
972
|
+
# Cloudflare quick tunnels can be flaky - retry with fresh tunnels if needed
|
|
973
|
+
ensure_cloudflared_installed()
|
|
974
|
+
|
|
975
|
+
api_key = self.api_key or self._get_api_key()
|
|
976
|
+
max_tunnel_attempts = int(os.getenv("SYNTH_TUNNEL_MAX_ATTEMPTS", "3"))
|
|
977
|
+
|
|
978
|
+
for tunnel_attempt in range(max_tunnel_attempts):
|
|
979
|
+
if tunnel_attempt > 0:
|
|
980
|
+
logger.warning(
|
|
981
|
+
f"Tunnel attempt {tunnel_attempt + 1}/{max_tunnel_attempts} - "
|
|
982
|
+
"requesting fresh tunnel..."
|
|
983
|
+
)
|
|
984
|
+
# Kill the previous tunnel process if it exists
|
|
985
|
+
if self._tunnel_proc:
|
|
986
|
+
try:
|
|
987
|
+
self._tunnel_proc.terminate()
|
|
988
|
+
await asyncio.sleep(1)
|
|
989
|
+
except Exception:
|
|
990
|
+
pass
|
|
991
|
+
|
|
992
|
+
logger.info("Opening Cloudflare quick tunnel...")
|
|
993
|
+
try:
|
|
994
|
+
self.url, self._tunnel_proc = await open_quick_tunnel_with_dns_verification(
|
|
995
|
+
self.port, api_key=api_key
|
|
996
|
+
)
|
|
997
|
+
except Exception as e:
|
|
998
|
+
logger.warning(f"Tunnel creation failed: {e}")
|
|
999
|
+
if tunnel_attempt == max_tunnel_attempts - 1:
|
|
1000
|
+
raise
|
|
1001
|
+
continue
|
|
1002
|
+
|
|
1003
|
+
# Apply hostname override if provided
|
|
1004
|
+
if override_host:
|
|
1005
|
+
parsed = urlparse(self.url)
|
|
1006
|
+
self.url = f"{parsed.scheme}://{override_host}"
|
|
1007
|
+
logger.info(f"Overriding hostname: {self.url}")
|
|
1008
|
+
|
|
1009
|
+
logger.info(f"Tunnel opened: {self.url}")
|
|
1010
|
+
|
|
1011
|
+
# Extra guard: wait for tunnel HTTP routing to become ready (not just DNS)
|
|
1012
|
+
ready = await _verify_tunnel_ready(
|
|
1013
|
+
self.url,
|
|
1014
|
+
api_key,
|
|
1015
|
+
verify_tls=_should_verify_tls(),
|
|
1016
|
+
)
|
|
1017
|
+
if ready:
|
|
1018
|
+
logger.info(f"Tunnel verified and ready: {self.url}")
|
|
1019
|
+
break
|
|
1020
|
+
else:
|
|
1021
|
+
logger.warning(
|
|
1022
|
+
f"Tunnel {self.url} not routing traffic after verification. "
|
|
1023
|
+
f"{'Retrying with fresh tunnel...' if tunnel_attempt < max_tunnel_attempts - 1 else 'Giving up.'}"
|
|
1024
|
+
)
|
|
1025
|
+
if tunnel_attempt == max_tunnel_attempts - 1:
|
|
1026
|
+
raise RuntimeError(
|
|
1027
|
+
f"Failed to establish working tunnel after {max_tunnel_attempts} attempts. "
|
|
1028
|
+
f"Last tunnel URL: {self.url}. "
|
|
1029
|
+
"This may indicate Cloudflare rate limiting or network issues. "
|
|
1030
|
+
"Try: SYNTH_TUNNEL_MODE=local if the backend can reach localhost, "
|
|
1031
|
+
"or use a named Cloudflare tunnel instead of quick tunnels."
|
|
1032
|
+
)
|
|
1033
|
+
else:
|
|
1034
|
+
raise ValueError(f"Unknown SYNTH_TUNNEL_MODE: {mode}")
|
|
1035
|
+
|
|
1036
|
+
# Register for signal handling
|
|
1037
|
+
_registered_instances.add(self)
|
|
1038
|
+
|
|
1039
|
+
# Call on_start callback if provided
|
|
1040
|
+
if self.on_start:
|
|
1041
|
+
try:
|
|
1042
|
+
self.on_start(self)
|
|
1043
|
+
except Exception as e:
|
|
1044
|
+
logger.warning(f"on_start callback raised exception: {e}")
|
|
1045
|
+
|
|
1046
|
+
return self
|
|
1047
|
+
|
|
1048
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
1049
|
+
"""Stop tunnel and server."""
|
|
1050
|
+
logger.info("Stopping in-process task app...")
|
|
1051
|
+
|
|
1052
|
+
# Unregister from signal handling
|
|
1053
|
+
_registered_instances.discard(self)
|
|
1054
|
+
|
|
1055
|
+
# Call on_stop callback if provided
|
|
1056
|
+
if self.on_stop:
|
|
1057
|
+
try:
|
|
1058
|
+
self.on_stop(self)
|
|
1059
|
+
except Exception as e:
|
|
1060
|
+
logger.warning(f"on_stop callback raised exception: {e}")
|
|
1061
|
+
|
|
1062
|
+
# Stop tunnel
|
|
1063
|
+
if self._tunnel_proc:
|
|
1064
|
+
logger.debug("Stopping Cloudflare tunnel...")
|
|
1065
|
+
stop_tunnel(self._tunnel_proc)
|
|
1066
|
+
self._tunnel_proc = None
|
|
1067
|
+
logger.info("Tunnel stopped")
|
|
1068
|
+
|
|
1069
|
+
# Stop the uvicorn server thread gracefully to avoid killing the host process
|
|
1070
|
+
if self._server_thread and self._server_thread.is_alive():
|
|
1071
|
+
logger.debug("Stopping uvicorn server thread...")
|
|
1072
|
+
if self._uvicorn_server:
|
|
1073
|
+
self._uvicorn_server.should_exit = True
|
|
1074
|
+
self._server_thread.join(timeout=5.0)
|
|
1075
|
+
if self._server_thread.is_alive():
|
|
1076
|
+
if self._uvicorn_server:
|
|
1077
|
+
# Last resort if graceful shutdown hangs
|
|
1078
|
+
self._uvicorn_server.force_exit = True
|
|
1079
|
+
self._server_thread.join(timeout=1.0)
|
|
1080
|
+
if self._server_thread.is_alive():
|
|
1081
|
+
logger.warning(
|
|
1082
|
+
"Uvicorn server thread did not stop cleanly; "
|
|
1083
|
+
"it will exit with the main process"
|
|
1084
|
+
)
|
|
1085
|
+
self._server_thread = None
|
|
1086
|
+
self._uvicorn_server = None
|
|
1087
|
+
|
|
1088
|
+
def _get_api_key(self) -> str:
|
|
1089
|
+
"""Get API key from environment or default."""
|
|
1090
|
+
import os
|
|
1091
|
+
|
|
1092
|
+
# Try to load .env file if available
|
|
1093
|
+
try:
|
|
1094
|
+
from dotenv import load_dotenv
|
|
1095
|
+
load_dotenv(override=False)
|
|
1096
|
+
except ImportError:
|
|
1097
|
+
pass
|
|
1098
|
+
|
|
1099
|
+
return os.getenv("ENVIRONMENT_API_KEY", "test")
|
|
1100
|
+
|
|
1101
|
+
async def _fetch_tunnel_config(self, api_key: str) -> dict:
|
|
1102
|
+
"""Fetch the customer's tunnel configuration from the backend.
|
|
1103
|
+
|
|
1104
|
+
Uses the existing /api/v1/tunnels/tunnel endpoint to get the customer's
|
|
1105
|
+
active tunnels. Returns the first active tunnel's config.
|
|
1106
|
+
|
|
1107
|
+
Returns a dict with:
|
|
1108
|
+
- hostname: The customer's configured tunnel hostname (e.g., "myapp.usesynth.ai")
|
|
1109
|
+
- tunnel_token: The cloudflared tunnel token for running the tunnel
|
|
1110
|
+
- local_port: The local port the tunnel routes to
|
|
1111
|
+
- local_host: The local host the tunnel routes to
|
|
1112
|
+
"""
|
|
1113
|
+
from synth_ai.core.env import get_backend_url
|
|
1114
|
+
|
|
1115
|
+
backend_url = get_backend_url()
|
|
1116
|
+
url = f"{backend_url}/api/v1/tunnels/"
|
|
1117
|
+
|
|
1118
|
+
async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
|
1119
|
+
try:
|
|
1120
|
+
resp = await client.get(
|
|
1121
|
+
url,
|
|
1122
|
+
headers={
|
|
1123
|
+
"Authorization": f"Bearer {api_key}",
|
|
1124
|
+
"X-API-Key": api_key,
|
|
1125
|
+
},
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
if resp.status_code == 404:
|
|
1129
|
+
logger.debug("No tunnels found for this API key")
|
|
1130
|
+
return {}
|
|
1131
|
+
|
|
1132
|
+
if resp.status_code == 401:
|
|
1133
|
+
raise RuntimeError(
|
|
1134
|
+
"Invalid API key. Please check your SYNTH_API_KEY."
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
resp.raise_for_status()
|
|
1138
|
+
tunnels = resp.json()
|
|
1139
|
+
|
|
1140
|
+
# Return the first active tunnel
|
|
1141
|
+
if tunnels and len(tunnels) > 0:
|
|
1142
|
+
tunnel = tunnels[0]
|
|
1143
|
+
return {
|
|
1144
|
+
"hostname": tunnel.get("hostname"),
|
|
1145
|
+
"tunnel_token": tunnel.get("tunnel_token"),
|
|
1146
|
+
"local_port": tunnel.get("local_port", 8000),
|
|
1147
|
+
"local_host": tunnel.get("local_host", "127.0.0.1"),
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
return {}
|
|
1151
|
+
|
|
1152
|
+
except httpx.HTTPStatusError as e:
|
|
1153
|
+
logger.warning(f"Failed to fetch tunnel config: {e}")
|
|
1154
|
+
return {}
|
|
1155
|
+
except Exception as e:
|
|
1156
|
+
logger.debug(f"Tunnel config fetch failed: {e}")
|
|
1157
|
+
return {}
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def _setup_signal_handlers() -> None:
|
|
1161
|
+
"""Set up signal handlers for graceful shutdown."""
|
|
1162
|
+
|
|
1163
|
+
def signal_handler(signum, frame):
|
|
1164
|
+
"""Handle SIGINT/SIGTERM by cleaning up all registered instances."""
|
|
1165
|
+
logger.info(f"Received signal {signum}, cleaning up {len(_registered_instances)} instances...")
|
|
1166
|
+
for instance in list(_registered_instances):
|
|
1167
|
+
try:
|
|
1168
|
+
# Trigger cleanup
|
|
1169
|
+
if instance._tunnel_proc:
|
|
1170
|
+
stop_tunnel(instance._tunnel_proc)
|
|
1171
|
+
instance._tunnel_proc = None
|
|
1172
|
+
except Exception as e:
|
|
1173
|
+
logger.error(f"Error cleaning up instance: {e}")
|
|
1174
|
+
_registered_instances.clear()
|
|
1175
|
+
|
|
1176
|
+
# Register handlers (only once)
|
|
1177
|
+
if not hasattr(_setup_signal_handlers, "_registered"):
|
|
1178
|
+
signal.signal(signal.SIGINT, signal_handler) # type: ignore[misc]
|
|
1179
|
+
signal.signal(signal.SIGTERM, signal_handler) # type: ignore[misc]
|
|
1180
|
+
_setup_signal_handlers._registered = True # type: ignore[attr-defined]
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
def _should_verify_tls() -> bool:
|
|
1184
|
+
"""Return True unless explicitly disabled via env."""
|
|
1185
|
+
val = (os.getenv("SYNTH_TUNNEL_VERIFY_TLS") or "true").strip().lower()
|
|
1186
|
+
return val not in ("0", "false", "no", "off")
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
# Set up signal handlers on module import
|
|
1190
|
+
_setup_signal_handlers()
|