openrunner-sdk 2.24.0__tar.gz → 2.24.2__tar.gz

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.
Files changed (167) hide show
  1. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/PKG-INFO +1 -1
  2. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/__init__.py +1 -1
  3. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/api_client.py +1 -1
  4. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/cli.py +36 -5
  5. openrunner_sdk-2.24.2/openrunner/integration/skypilot.py +447 -0
  6. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/mcp_server.py +79 -0
  7. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/pyproject.toml +1 -1
  8. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/.gitignore +0 -0
  9. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/=6.0 +0 -0
  10. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/=8.1 +0 -0
  11. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/README.md +0 -0
  12. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/CLAUDE.md +0 -0
  13. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/artifact.py +0 -0
  14. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/buffer.py +0 -0
  15. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/cache.py +0 -0
  16. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/config.py +0 -0
  17. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/cost.py +0 -0
  18. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/dataset.py +0 -0
  19. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/environment.py +0 -0
  20. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/evaluation.py +0 -0
  21. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/feedback.py +0 -0
  22. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/git_info.py +0 -0
  23. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/__init__.py +0 -0
  24. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/api_integration.py +0 -0
  25. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/cost_guard.py +0 -0
  26. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/gpu_types.py +0 -0
  27. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/price_compare.py +0 -0
  28. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/provider_base.py +0 -0
  29. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/__init__.py +0 -0
  30. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/lambda_cloud.py +0 -0
  31. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/modal_provider.py +0 -0
  32. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/ovhcloud.py +0 -0
  33. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/runpod.py +0 -0
  34. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/providers/vastai.py +0 -0
  35. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/gpu/workspace_sync.py +0 -0
  36. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/guardrails.py +0 -0
  37. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/hooks/patent-lab-post-commit +0 -0
  38. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/install_commands.py +0 -0
  39. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/__init__.py +0 -0
  40. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/accelerate.py +0 -0
  41. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/anthropic_tracer.py +0 -0
  42. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/catboost.py +0 -0
  43. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/diffusers.py +0 -0
  44. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/fastai.py +0 -0
  45. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/forced_alignment.py +0 -0
  46. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/gladia.py +0 -0
  47. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/gymnasium.py +0 -0
  48. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/huggingface.py +0 -0
  49. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/hydra.py +0 -0
  50. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/ignite.py +0 -0
  51. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/jax.py +0 -0
  52. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/keras.py +0 -0
  53. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/langchain.py +0 -0
  54. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/lightgbm.py +0 -0
  55. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/lightning.py +0 -0
  56. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/llamaindex.py +0 -0
  57. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/openai_finetune.py +0 -0
  58. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/openai_tracer.py +0 -0
  59. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/optuna.py +0 -0
  60. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/pytorch.py +0 -0
  61. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/sb3.py +0 -0
  62. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/sklearn.py +0 -0
  63. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/tensorflow.py +0 -0
  64. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/trl.py +0 -0
  65. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/tts.py +0 -0
  66. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/ultralytics.py +0 -0
  67. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/voice_agent.py +0 -0
  68. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/whisper.py +0 -0
  69. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/integration/xgboost.py +0 -0
  70. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/launch.py +0 -0
  71. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/media.py +0 -0
  72. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/migrate.py +0 -0
  73. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/model.py +0 -0
  74. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/offline.py +0 -0
  75. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/patent_scan.py +0 -0
  76. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/pii.py +0 -0
  77. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/plot.py +0 -0
  78. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/prompt.py +0 -0
  79. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/query_api.py +0 -0
  80. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/redact.py +0 -0
  81. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/run.py +0 -0
  82. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/scorers.py +0 -0
  83. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/sender.py +0 -0
  84. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/session.py +0 -0
  85. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/settings.py +0 -0
  86. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/summary.py +0 -0
  87. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/sweep.py +0 -0
  88. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/system_metrics.py +0 -0
  89. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/tensorboard.py +0 -0
  90. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/tools/__init__.py +0 -0
  91. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/tools/patent_report.py +0 -0
  92. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/trace.py +0 -0
  93. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/transcript_formatter.py +0 -0
  94. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/wal.py +0 -0
  95. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/wandb_compat/__init__.py +0 -0
  96. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/wandb_compat/_shim.py +0 -0
  97. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/openrunner/wer.py +0 -0
  98. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/__init__.py +0 -0
  99. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/conftest.py +0 -0
  100. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
  101. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
  102. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
  103. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
  104. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
  105. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
  106. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
  107. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
  108. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
  109. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
  110. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
  111. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
  112. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/integration/__init__.py +0 -0
  113. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/integration/conftest.py +0 -0
  114. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/integration/test_artifact_round_trip.py +0 -0
  115. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_alert.py +0 -0
  116. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_aliases.py +0 -0
  117. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_api_client.py +0 -0
  118. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_artifact.py +0 -0
  119. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_buffer.py +0 -0
  120. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_cache.py +0 -0
  121. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_class_scorers.py +0 -0
  122. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_cli.py +0 -0
  123. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_config.py +0 -0
  124. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_evaluation.py +0 -0
  125. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_finish.py +0 -0
  126. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_git_info.py +0 -0
  127. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/__init__.py +0 -0
  128. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/conftest.py +0 -0
  129. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_cost_guard.py +0 -0
  130. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_gpu_types.py +0 -0
  131. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_lambda.py +0 -0
  132. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_modal.py +0 -0
  133. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_ovhcloud.py +0 -0
  134. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_price_compare.py +0 -0
  135. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_provider_base.py +0 -0
  136. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_runpod.py +0 -0
  137. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_sdk_gpu_api.py +0 -0
  138. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_vastai.py +0 -0
  139. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_gpu/test_workspace_sync.py +0 -0
  140. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_init.py +0 -0
  141. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_fastai.py +0 -0
  142. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_huggingface.py +0 -0
  143. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_keras.py +0 -0
  144. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_langchain.py +0 -0
  145. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_lightning.py +0 -0
  146. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_pytorch.py +0 -0
  147. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_sklearn.py +0 -0
  148. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_integration_xgboost.py +0 -0
  149. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_launch.py +0 -0
  150. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_log.py +0 -0
  151. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_log_code.py +0 -0
  152. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_mcp_predictions.py +0 -0
  153. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_media.py +0 -0
  154. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_migrate.py +0 -0
  155. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_offline.py +0 -0
  156. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_offline_sync.py +0 -0
  157. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_pii.py +0 -0
  158. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_plot.py +0 -0
  159. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_query_api.py +0 -0
  160. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_resume.py +0 -0
  161. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_sdk_features.py +0 -0
  162. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_sender.py +0 -0
  163. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_summary.py +0 -0
  164. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_sweep.py +0 -0
  165. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_system_metrics.py +0 -0
  166. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_trace.py +0 -0
  167. {openrunner_sdk-2.24.0 → openrunner_sdk-2.24.2}/tests/test_wandb_compat.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrunner-sdk
3
- Version: 2.24.0
3
+ Version: 2.24.2
4
4
  Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
5
5
  Project-URL: Homepage, https://github.com/jqueguiner/openrunner
6
6
  Project-URL: Repository, https://github.com/jqueguiner/openrunner
@@ -121,7 +121,7 @@ launch.from_run = _launch_from_run # type: ignore[attr-defined]
121
121
  # openrunner.trace.patch_openai() syntax
122
122
  trace.patch_openai = _patch_openai # type: ignore[attr-defined]
123
123
 
124
- __version__ = "2.24.0"
124
+ __version__ = "2.24.2"
125
125
 
126
126
  logger = logging.getLogger("openrunner")
127
127
 
@@ -89,7 +89,7 @@ class APIClient:
89
89
 
90
90
  def _request(self, method: str, path: str, **kwargs) -> httpx.Response:
91
91
  """Make an HTTP request with automatic retry on transient failures."""
92
- return _retry_request(getattr(self._client, method), path, **kwargs)
92
+ return _retry_request(getattr(self._client, method.lower()), path, **kwargs)
93
93
 
94
94
  def create_run(self, data: dict[str, Any]) -> dict[str, Any] | None:
95
95
  """POST /runs -- create a new run on the server.
@@ -3294,6 +3294,22 @@ def patent_scan(
3294
3294
  click.echo("Error: --project required or set in .openrunner/settings.json", err=True)
3295
3295
  sys.exit(1)
3296
3296
 
3297
+ # Resolve project name → UUID if needed
3298
+ project_id = project
3299
+ if project and api_key and not dry_run:
3300
+ try:
3301
+ _client = APIClient(api_key=api_key, base_url=base_url)
3302
+ _resp = _client._request("get", "/projects")
3303
+ if _resp.status_code == 200:
3304
+ for p in _resp.json():
3305
+ org = p.get("org_name", "")
3306
+ name = p.get("name", "")
3307
+ if f"{org}/{name}" == project or name == project:
3308
+ project_id = p["id"]
3309
+ break
3310
+ except Exception:
3311
+ pass # fall back to using project as-is
3312
+
3297
3313
  # Determine commits to scan
3298
3314
  if commit:
3299
3315
  commits = [commit]
@@ -3350,12 +3366,12 @@ def patent_scan(
3350
3366
  click.echo(f" CPC: {', '.join(la.cpc_classes)}")
3351
3367
 
3352
3368
  # Upload
3353
- if not dry_run and api_key and project:
3369
+ if not dry_run and api_key and project_id:
3354
3370
  try:
3355
3371
  client = APIClient(api_key=api_key, base_url=base_url)
3356
3372
  resp = client._request(
3357
- "POST",
3358
- f"/projects/{project}/patent-lab/entries",
3373
+ "post",
3374
+ f"/projects/{project_id}/patent-lab/entries",
3359
3375
  json=result.to_api_payload(),
3360
3376
  )
3361
3377
  if resp.status_code == 201:
@@ -3395,6 +3411,21 @@ def patent_lab(project: str | None, min_score: int, status: str | None, limit: i
3395
3411
  sys.exit(1)
3396
3412
 
3397
3413
  client = APIClient(api_key=api_key, base_url=base_url)
3414
+
3415
+ # Resolve project name → UUID
3416
+ project_id = project
3417
+ try:
3418
+ _resp = client._request("get", "/projects")
3419
+ if _resp.status_code == 200:
3420
+ for p in _resp.json():
3421
+ org = p.get("org_name", "")
3422
+ name = p.get("name", "")
3423
+ if f"{org}/{name}" == project or name == project:
3424
+ project_id = p["id"]
3425
+ break
3426
+ except Exception:
3427
+ pass
3428
+
3398
3429
  params: dict = {"limit": limit}
3399
3430
  if min_score:
3400
3431
  params["min_score"] = min_score
@@ -3403,8 +3434,8 @@ def patent_lab(project: str | None, min_score: int, status: str | None, limit: i
3403
3434
 
3404
3435
  try:
3405
3436
  resp = client._request(
3406
- "GET",
3407
- f"/projects/{project}/patent-lab/entries",
3437
+ "get",
3438
+ f"/projects/{project_id}/patent-lab/entries",
3408
3439
  params=params,
3409
3440
  )
3410
3441
  data = resp.json()
@@ -0,0 +1,447 @@
1
+ """SkyPilot integration for OpenRunner.
2
+
3
+ Launch training jobs on any cloud with automatic cost tracking,
4
+ spot recovery awareness, and experiment run linking.
5
+
6
+ SkyPilot (Apache 2.0) provides multi-cloud GPU orchestration.
7
+ This integration wraps sky.launch() to auto-inject OpenRunner
8
+ env vars and map $SKYPILOT_TASK_ID to run IDs for unified
9
+ tracking across spot preemptions.
10
+
11
+ Usage:
12
+ import openrunner
13
+ from openrunner.integration.skypilot import launch, managed_job
14
+
15
+ # Launch a training job on cheapest available GPU
16
+ cluster = launch(
17
+ task_yaml="train.yaml",
18
+ project="my-project",
19
+ name="gpt-finetune",
20
+ gpus="A100:4",
21
+ use_spot=True,
22
+ )
23
+
24
+ # Or use managed jobs (auto-recovery on preemption)
25
+ job = managed_job(
26
+ task_yaml="train.yaml",
27
+ project="my-project",
28
+ name="long-training",
29
+ gpus="A100:8",
30
+ )
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import json
36
+ import logging
37
+ import os
38
+ import subprocess
39
+ import tempfile
40
+ from pathlib import Path
41
+ from typing import Any
42
+
43
+ logger = logging.getLogger("openrunner.skypilot")
44
+
45
+
46
+ def _check_skypilot() -> None:
47
+ """Verify SkyPilot is installed."""
48
+ try:
49
+ import sky # noqa: F401
50
+ except ImportError:
51
+ raise ImportError(
52
+ "SkyPilot is not installed. Install with: "
53
+ "pip install 'skypilot[aws]' or pip install 'skypilot[gcp]' etc."
54
+ )
55
+
56
+
57
+ def _get_openrunner_envs(project: str | None = None) -> dict[str, str]:
58
+ """Build env vars to inject into SkyPilot tasks for OpenRunner tracking."""
59
+ from openrunner.settings import SDKSettings
60
+
61
+ settings = SDKSettings()
62
+ envs: dict[str, str] = {}
63
+
64
+ # API key
65
+ if settings.api_key:
66
+ envs["OPENRUNNER_API_KEY"] = settings.api_key
67
+ elif os.environ.get("OPENRUNNER_API_KEY"):
68
+ envs["OPENRUNNER_API_KEY"] = os.environ["OPENRUNNER_API_KEY"]
69
+
70
+ # Base URL
71
+ if settings.base_url:
72
+ envs["OPENRUNNER_BASE_URL"] = settings.base_url
73
+
74
+ # Project
75
+ if project:
76
+ envs["OPENRUNNER_PROJECT"] = project
77
+ elif settings.project:
78
+ envs["OPENRUNNER_PROJECT"] = settings.project
79
+
80
+ return envs
81
+
82
+
83
+ def _inject_envs_into_yaml(yaml_path: str, envs: dict[str, str]) -> str:
84
+ """Read a SkyPilot YAML, inject envs, return path to modified YAML."""
85
+ import yaml as pyyaml
86
+
87
+ with open(yaml_path) as f:
88
+ task = pyyaml.safe_load(f)
89
+
90
+ existing = task.get("envs", {}) or {}
91
+ existing.update(envs)
92
+
93
+ # Add setup command to install openrunner-sdk if not present
94
+ setup = task.get("setup", "") or ""
95
+ if "openrunner" not in setup:
96
+ setup = "pip install openrunner-sdk > /dev/null 2>&1\n" + setup
97
+ task["setup"] = setup
98
+
99
+ task["envs"] = existing
100
+
101
+ # Write to temp file
102
+ tmp = tempfile.NamedTemporaryFile(
103
+ mode="w", suffix=".yaml", prefix="openrunner-sky-",
104
+ delete=False,
105
+ )
106
+ pyyaml.dump(task, tmp, default_flow_style=False)
107
+ tmp.close()
108
+ return tmp.name
109
+
110
+
111
+ def launch(
112
+ task_yaml: str,
113
+ *,
114
+ project: str | None = None,
115
+ name: str | None = None,
116
+ cluster: str | None = None,
117
+ gpus: str | None = None,
118
+ use_spot: bool = False,
119
+ idle_minutes: int | None = 10,
120
+ down: bool = False,
121
+ env: dict[str, str] | None = None,
122
+ dryrun: bool = False,
123
+ ) -> dict[str, Any]:
124
+ """Launch a SkyPilot task with OpenRunner tracking auto-configured.
125
+
126
+ Injects OPENRUNNER_API_KEY, OPENRUNNER_BASE_URL, and OPENRUNNER_PROJECT
127
+ into the task's environment. The training script should use
128
+ $SKYPILOT_TASK_ID as the run name for unified tracking across
129
+ spot recovery attempts.
130
+
131
+ Args:
132
+ task_yaml: Path to SkyPilot task YAML file.
133
+ project: OpenRunner project name (auto-detected if not set).
134
+ name: Cluster name (auto-generated if not set).
135
+ cluster: Existing cluster to reuse.
136
+ gpus: GPU spec override (e.g., "A100:4", "H100:8").
137
+ use_spot: Use spot/preemptible instances.
138
+ idle_minutes: Auto-stop after N idle minutes (None to disable).
139
+ down: Tear down cluster after job finishes.
140
+ env: Additional environment variables.
141
+ dryrun: If True, print the command without executing.
142
+
143
+ Returns:
144
+ Dict with cluster_name, status, and job info.
145
+ """
146
+ _check_skypilot()
147
+
148
+ # Build env vars
149
+ envs = _get_openrunner_envs(project)
150
+ if env:
151
+ envs.update(env)
152
+
153
+ # Add spot recovery hint
154
+ envs["OPENRUNNER_SPOT_RECOVERY"] = "1" if use_spot else "0"
155
+
156
+ # Inject into YAML
157
+ modified_yaml = _inject_envs_into_yaml(task_yaml, envs)
158
+
159
+ try:
160
+ import sky
161
+
162
+ task = sky.Task.from_yaml(modified_yaml)
163
+
164
+ if gpus:
165
+ task.set_resources(sky.Resources(accelerators=gpus, use_spot=use_spot))
166
+ elif use_spot:
167
+ resources = task.resources
168
+ if resources:
169
+ res = list(resources)[0]
170
+ task.set_resources(res.copy(use_spot=True))
171
+
172
+ if dryrun:
173
+ logger.info("Dryrun: would launch %s with envs: %s", task_yaml, list(envs.keys()))
174
+ return {"status": "dryrun", "yaml": modified_yaml, "envs": list(envs.keys())}
175
+
176
+ cluster_name = cluster or name
177
+ kwargs: dict[str, Any] = {}
178
+ if cluster_name:
179
+ kwargs["cluster_name"] = cluster_name
180
+ if idle_minutes is not None:
181
+ kwargs["idle_minutes_to_autostop"] = idle_minutes
182
+ if down:
183
+ kwargs["down"] = True
184
+
185
+ request_id = sky.launch(task, **kwargs)
186
+
187
+ result = {
188
+ "status": "launched",
189
+ "cluster_name": cluster_name,
190
+ "request_id": str(request_id) if request_id else None,
191
+ "use_spot": use_spot,
192
+ "envs_injected": list(envs.keys()),
193
+ }
194
+ logger.info("SkyPilot launch: %s", result)
195
+ return result
196
+
197
+ finally:
198
+ # Cleanup temp file
199
+ try:
200
+ os.unlink(modified_yaml)
201
+ except OSError:
202
+ pass
203
+
204
+
205
+ def managed_job(
206
+ task_yaml: str,
207
+ *,
208
+ project: str | None = None,
209
+ name: str | None = None,
210
+ gpus: str | None = None,
211
+ env: dict[str, str] | None = None,
212
+ dryrun: bool = False,
213
+ ) -> dict[str, Any]:
214
+ """Submit a SkyPilot managed job with auto-recovery and OpenRunner tracking.
215
+
216
+ Managed jobs automatically recover from spot preemptions and hardware
217
+ failures. Use $SKYPILOT_TASK_ID in your training script as the
218
+ OpenRunner run name to unify metrics across recovery attempts.
219
+
220
+ Example training script:
221
+ import os, openrunner
222
+ run = openrunner.init(
223
+ project=os.environ.get("OPENRUNNER_PROJECT", "default"),
224
+ name=os.environ.get("SKYPILOT_TASK_ID", "local"),
225
+ resume="allow", # Resume from checkpoint on recovery
226
+ )
227
+ """
228
+ _check_skypilot()
229
+
230
+ envs = _get_openrunner_envs(project)
231
+ envs["OPENRUNNER_SPOT_RECOVERY"] = "1"
232
+ if env:
233
+ envs.update(env)
234
+
235
+ modified_yaml = _inject_envs_into_yaml(task_yaml, envs)
236
+
237
+ try:
238
+ import sky
239
+
240
+ task = sky.Task.from_yaml(modified_yaml)
241
+
242
+ if gpus:
243
+ task.set_resources(sky.Resources(accelerators=gpus, use_spot=True))
244
+
245
+ if dryrun:
246
+ logger.info("Dryrun: would submit managed job %s", task_yaml)
247
+ return {"status": "dryrun", "yaml": modified_yaml}
248
+
249
+ kwargs: dict[str, Any] = {}
250
+ if name:
251
+ kwargs["name"] = name
252
+
253
+ request_id = sky.jobs.launch(task, **kwargs)
254
+
255
+ result = {
256
+ "status": "submitted",
257
+ "name": name,
258
+ "request_id": str(request_id) if request_id else None,
259
+ "envs_injected": list(envs.keys()),
260
+ }
261
+ logger.info("SkyPilot managed job: %s", result)
262
+ return result
263
+
264
+ finally:
265
+ try:
266
+ os.unlink(modified_yaml)
267
+ except OSError:
268
+ pass
269
+
270
+
271
+ def get_cluster_cost(cluster_name: str) -> dict[str, Any] | None:
272
+ """Query SkyPilot for cluster cost information.
273
+
274
+ Returns estimated cost data from the cluster's lifecycle.
275
+ """
276
+ try:
277
+ import sky
278
+ status = sky.status(cluster_names=[cluster_name])
279
+ if not status:
280
+ return None
281
+
282
+ info = status[0]
283
+ return {
284
+ "cluster_name": cluster_name,
285
+ "cloud": str(info.get("cloud", "")),
286
+ "instance_type": str(info.get("instance_type", "")),
287
+ "accelerators": str(info.get("accelerators", "")),
288
+ "use_spot": info.get("use_spot", False),
289
+ "region": str(info.get("region", "")),
290
+ "status": str(info.get("status", "")),
291
+ "launched_at": str(info.get("launched_at", "")),
292
+ "last_use": str(info.get("last_use", "")),
293
+ }
294
+ except Exception as e:
295
+ logger.warning("Failed to get cluster cost for %s: %s", cluster_name, e)
296
+ return None
297
+
298
+
299
+ def launch_with_catalog(
300
+ task_yaml: str,
301
+ *,
302
+ org_id: str,
303
+ table: str = "utterances",
304
+ filter: str = "",
305
+ export_format: str = "jsonl",
306
+ limit: int = 0,
307
+ mount_path: str = "/mnt/catalog",
308
+ project: str | None = None,
309
+ gpus: str | None = None,
310
+ use_spot: bool = False,
311
+ env: dict[str, str] | None = None,
312
+ dryrun: bool = False,
313
+ ) -> dict[str, Any]:
314
+ """Launch a SkyPilot task with OpenRunner catalog data auto-mounted.
315
+
316
+ Queries the catalog API for the storage connection, then injects
317
+ file_mounts and catalog env vars into the SkyPilot YAML. The
318
+ training script can access catalog data at mount_path.
319
+
320
+ Example:
321
+ from openrunner.integration.skypilot import launch_with_catalog
322
+
323
+ launch_with_catalog(
324
+ "train.yaml",
325
+ org_id="my-org-uuid",
326
+ table="utterances",
327
+ filter="primary_language = 'en' AND duration_s > 1.0",
328
+ gpus="A100:4",
329
+ use_spot=True,
330
+ )
331
+
332
+ The training script should:
333
+ 1. Read the manifest at /tmp/manifest.jsonl (exported via CLI)
334
+ 2. Or directly access /mnt/catalog/ for raw LanceDB data
335
+ 3. Use $SKYPILOT_TASK_ID as the OpenRunner run name
336
+ """
337
+ _check_skypilot()
338
+
339
+ from openrunner.api_client import APIClient
340
+ from openrunner.settings import SDKSettings
341
+
342
+ settings = SDKSettings()
343
+ client = APIClient(
344
+ base_url=settings.base_url or "https://openrun.gladia.io/api/v1",
345
+ api_key=settings.api_key or "",
346
+ )
347
+
348
+ # Fetch catalog connection info from the API
349
+ try:
350
+ resp = client._session.get(
351
+ f"{client._base_url}/organizations/{org_id}/catalog/status",
352
+ )
353
+ resp.raise_for_status()
354
+ status = resp.json()
355
+ except Exception as e:
356
+ logger.error("Failed to fetch catalog status: %s", e)
357
+ raise RuntimeError(f"Cannot access catalog for org {org_id}: {e}")
358
+ finally:
359
+ client.close()
360
+
361
+ if not status.get("configured"):
362
+ raise RuntimeError("No catalog storage connection configured for this organization")
363
+
364
+ bucket = status["bucket"]
365
+ prefix = status.get("prefix", "")
366
+ endpoint = status.get("endpoint", "")
367
+
368
+ # Build S3 source for file_mounts
369
+ s3_source = f"s3://{bucket}"
370
+ if prefix:
371
+ s3_source += f"/{prefix}"
372
+
373
+ # Inject catalog env vars + file_mounts into YAML
374
+ import yaml as pyyaml
375
+
376
+ with open(task_yaml) as f:
377
+ task = pyyaml.safe_load(f)
378
+
379
+ # Add file_mounts
380
+ mounts = task.get("file_mounts", {}) or {}
381
+ mounts[mount_path] = {"source": s3_source, "mode": "MOUNT"}
382
+ task["file_mounts"] = mounts
383
+
384
+ # Add catalog env vars
385
+ existing_envs = task.get("envs", {}) or {}
386
+ existing_envs.update({
387
+ "CATALOG_TABLE": table,
388
+ "CATALOG_FILTER": filter,
389
+ "CATALOG_FORMAT": export_format,
390
+ "CATALOG_LIMIT": str(limit) if limit else "0",
391
+ "CATALOG_S3_ENDPOINT": endpoint,
392
+ "CATALOG_S3_BUCKET": bucket,
393
+ "CATALOG_S3_PREFIX": prefix,
394
+ })
395
+ task["envs"] = existing_envs
396
+
397
+ # Write modified YAML
398
+ tmp = tempfile.NamedTemporaryFile(
399
+ mode="w", suffix=".yaml", prefix="openrunner-catalog-sky-",
400
+ delete=False,
401
+ )
402
+ pyyaml.dump(task, tmp, default_flow_style=False)
403
+ tmp.close()
404
+
405
+ # Delegate to regular launch
406
+ try:
407
+ return launch(
408
+ tmp.name,
409
+ project=project,
410
+ gpus=gpus,
411
+ use_spot=use_spot,
412
+ env=env,
413
+ dryrun=dryrun,
414
+ )
415
+ finally:
416
+ try:
417
+ os.unlink(tmp.name)
418
+ except OSError:
419
+ pass
420
+
421
+
422
+ def list_gpu_prices(accelerator: str = "A100") -> list[dict[str, Any]]:
423
+ """List GPU prices across clouds using SkyPilot's catalog.
424
+
425
+ Returns sorted list of (cloud, instance, region, price) for the
426
+ given accelerator type.
427
+ """
428
+ try:
429
+ import sky
430
+ gpus = sky.show_gpus(accelerator, return_data=True)
431
+ if not gpus:
432
+ return []
433
+ return [
434
+ {
435
+ "cloud": str(row.get("Cloud", "")),
436
+ "instance": str(row.get("Instance", "")),
437
+ "accelerator": str(row.get("Accelerator", "")),
438
+ "count": row.get("Count", 1),
439
+ "region": str(row.get("Region", "")),
440
+ "price_per_hour": row.get("Price", 0.0),
441
+ "spot_price": row.get("Spot Price", None),
442
+ }
443
+ for row in gpus
444
+ ]
445
+ except Exception as e:
446
+ logger.warning("Failed to list GPU prices: %s", e)
447
+ return []
@@ -517,6 +517,41 @@ def create_server():
517
517
  "required": ["idea"],
518
518
  },
519
519
  ),
520
+ Tool(
521
+ name="openrunner_patent_agent",
522
+ description=(
523
+ "Run a Claude Code agent on a patent lab entry SERVER-SIDE. "
524
+ "The server spawns a Claude Code agent with patent-creator MCP tools "
525
+ "to perform deep patent analysis. Tasks: analyze (patentability + prior art), "
526
+ "draft-claims, prior-art (comprehensive search), full-disclosure, "
527
+ "review (USPTO compliance). Results are stored in the patent lab entry."
528
+ ),
529
+ inputSchema={
530
+ "type": "object",
531
+ "properties": {
532
+ "entry_id": {
533
+ "type": "string",
534
+ "description": "Patent lab entry UUID to analyze",
535
+ },
536
+ "task": {
537
+ "type": "string",
538
+ "description": "Task: analyze, draft-claims, prior-art, full-disclosure, review",
539
+ "enum": ["analyze", "draft-claims", "prior-art", "full-disclosure", "review"],
540
+ "default": "analyze",
541
+ },
542
+ "project": {
543
+ "type": "string",
544
+ "description": "Project name (format: org/project)",
545
+ },
546
+ "max_budget_usd": {
547
+ "type": "number",
548
+ "description": "Max budget for agent run in USD (default: 2.0)",
549
+ "default": 2.0,
550
+ },
551
+ },
552
+ "required": ["entry_id"],
553
+ },
554
+ ),
520
555
  Tool(
521
556
  name="openrunner_patent_report",
522
557
  description=(
@@ -621,6 +656,8 @@ def create_server():
621
656
  result = await _tool_patent_lab_list(arguments)
622
657
  elif name == "openrunner_patent_analyze":
623
658
  result = await _tool_patent_analyze(arguments)
659
+ elif name == "openrunner_patent_agent":
660
+ result = await _tool_patent_agent(arguments)
624
661
  elif name == "openrunner_patent_report":
625
662
  result = await _tool_patent_report(arguments)
626
663
  else:
@@ -1829,6 +1866,48 @@ def _text_similarity(text1: str, text2: str) -> float:
1829
1866
  return len(intersection) / len(union)
1830
1867
 
1831
1868
 
1869
+ async def _tool_patent_agent(arguments: dict) -> str:
1870
+ """Run a Claude Code agent on a patent entry via server API."""
1871
+ project = arguments.get("project") or _resolve_project()
1872
+ if not project:
1873
+ return "No project specified."
1874
+
1875
+ entry_id = arguments.get("entry_id")
1876
+ if not entry_id:
1877
+ return "entry_id is required."
1878
+
1879
+ task = arguments.get("task", "analyze")
1880
+ max_budget = arguments.get("max_budget_usd", 2.0)
1881
+
1882
+ project_id = await _resolve_project_id(project)
1883
+ if not project_id:
1884
+ return f"Project '{project}' not found."
1885
+
1886
+ try:
1887
+ resp = await _api_post(
1888
+ f"/projects/{project_id}/patent-lab/entries/{entry_id}/agent",
1889
+ data={
1890
+ "task": task,
1891
+ "max_budget_usd": max_budget,
1892
+ },
1893
+ )
1894
+ status = resp.get("status", "unknown")
1895
+ output = resp.get("output", "")
1896
+ cost = resp.get("cost_usd", 0)
1897
+ duration = resp.get("duration_ms", 0)
1898
+
1899
+ return (
1900
+ f"Patent agent completed.\n"
1901
+ f" Task: {task}\n"
1902
+ f" Status: {status}\n"
1903
+ f" Cost: ${cost:.4f}\n"
1904
+ f" Duration: {duration}ms\n"
1905
+ f" Output:\n{output[:1500]}"
1906
+ )
1907
+ except Exception as e:
1908
+ return f"Error running patent agent: {e}"
1909
+
1910
+
1832
1911
  async def _tool_patent_report(arguments: dict) -> str:
1833
1912
  """Create a patent report memo via API."""
1834
1913
  project = arguments.get("project") or _resolve_project()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openrunner-sdk"
3
- version = "2.24.0"
3
+ version = "2.24.2"
4
4
  description = "OpenRunner SDK - W&B-compatible ML experiment tracking client"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
File without changes
File without changes