skypilot-nightly 1.0.0.dev20250806__py3-none-any.whl → 1.0.0.dev20250808__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 skypilot-nightly might be problematic. Click here for more details.

Files changed (137) hide show
  1. sky/__init__.py +2 -2
  2. sky/backends/backend_utils.py +20 -1
  3. sky/backends/cloud_vm_ray_backend.py +42 -6
  4. sky/check.py +11 -1
  5. sky/client/cli/command.py +248 -119
  6. sky/client/sdk.py +146 -66
  7. sky/client/sdk_async.py +5 -1
  8. sky/core.py +5 -2
  9. sky/dashboard/out/404.html +1 -1
  10. sky/dashboard/out/_next/static/-DXZksWqf2waNHeU9YTQe/_buildManifest.js +1 -0
  11. sky/dashboard/out/_next/static/chunks/1141-a8a8f1adba34c892.js +11 -0
  12. sky/dashboard/out/_next/static/chunks/1871-980a395e92633a5c.js +6 -0
  13. sky/dashboard/out/_next/static/chunks/3785.6003d293cb83eab4.js +1 -0
  14. sky/dashboard/out/_next/static/chunks/3850-ff4a9a69d978632b.js +1 -0
  15. sky/dashboard/out/_next/static/chunks/4725.29550342bd53afd8.js +1 -0
  16. sky/dashboard/out/_next/static/chunks/{4937.d6bf67771e353356.js → 4937.a2baa2df5572a276.js} +1 -1
  17. sky/dashboard/out/_next/static/chunks/6130-2be46d70a38f1e82.js +1 -0
  18. sky/dashboard/out/_next/static/chunks/6601-06114c982db410b6.js +1 -0
  19. sky/dashboard/out/_next/static/chunks/{691.6d99cbfba347cebf.js → 691.5eeedf82cc243343.js} +1 -1
  20. sky/dashboard/out/_next/static/chunks/6989-6129c1cfbcf51063.js +1 -0
  21. sky/dashboard/out/_next/static/chunks/6990-0f886f16e0d55ff8.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/8056-34d27f51e6d1c631.js +1 -0
  23. sky/dashboard/out/_next/static/chunks/8252.62b0d23aed618bb2.js +16 -0
  24. sky/dashboard/out/_next/static/chunks/8969-c9686994ddafcf01.js +1 -0
  25. sky/dashboard/out/_next/static/chunks/9025.a1bef12d672bb66d.js +6 -0
  26. sky/dashboard/out/_next/static/chunks/9159-11421c0f2909236f.js +1 -0
  27. sky/dashboard/out/_next/static/chunks/9360.85b0b1b4054574dd.js +31 -0
  28. sky/dashboard/out/_next/static/chunks/9666.cd4273f2a5c5802c.js +1 -0
  29. sky/dashboard/out/_next/static/chunks/{9847.4c46c5e229c78704.js → 9847.757720f3b40c0aa5.js} +1 -1
  30. sky/dashboard/out/_next/static/chunks/pages/{_app-2a43ea3241bbdacd.js → _app-491a4d699d95e808.js} +1 -1
  31. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-ae17cec0fc6483d9.js +11 -0
  32. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-155d477a6c3e04e2.js +1 -0
  33. sky/dashboard/out/_next/static/chunks/pages/{clusters-47f1ddae13a2f8e4.js → clusters-b30460f683e6ba96.js} +1 -1
  34. sky/dashboard/out/_next/static/chunks/pages/config-dfb9bf07b13045f4.js +1 -0
  35. sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-2a44e70b500b6b70.js → [context]-13d53fffc03ccb52.js} +1 -1
  36. sky/dashboard/out/_next/static/chunks/pages/{infra-22faac9325016d83.js → infra-fc9222e26c8e2f0d.js} +1 -1
  37. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-154f55cf8af55be5.js +11 -0
  38. sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-f5ccf5d39d87aebe.js +21 -0
  39. sky/dashboard/out/_next/static/chunks/pages/jobs-cdc60fb5d371e16a.js +1 -0
  40. sky/dashboard/out/_next/static/chunks/pages/{users-b90c865a690bfe84.js → users-7ed36e44e779d5c7.js} +1 -1
  41. sky/dashboard/out/_next/static/chunks/pages/{volumes-7af733f5d7b6ed1c.js → volumes-c9695d657f78b5dc.js} +1 -1
  42. sky/dashboard/out/_next/static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js +1 -0
  43. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-f72f73bcef9541dc.js +1 -0
  44. sky/dashboard/out/_next/static/chunks/pages/workspaces-8f67be60165724cc.js +1 -0
  45. sky/dashboard/out/_next/static/chunks/webpack-339efec49c0cc7d0.js +1 -0
  46. sky/dashboard/out/_next/static/css/4614e06482d7309e.css +3 -0
  47. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  48. sky/dashboard/out/clusters/[cluster].html +1 -1
  49. sky/dashboard/out/clusters.html +1 -1
  50. sky/dashboard/out/config.html +1 -1
  51. sky/dashboard/out/index.html +1 -1
  52. sky/dashboard/out/infra/[context].html +1 -1
  53. sky/dashboard/out/infra.html +1 -1
  54. sky/dashboard/out/jobs/[job].html +1 -1
  55. sky/dashboard/out/jobs/pools/[pool].html +1 -0
  56. sky/dashboard/out/jobs.html +1 -1
  57. sky/dashboard/out/users.html +1 -1
  58. sky/dashboard/out/volumes.html +1 -1
  59. sky/dashboard/out/workspace/new.html +1 -1
  60. sky/dashboard/out/workspaces/[name].html +1 -1
  61. sky/dashboard/out/workspaces.html +1 -1
  62. sky/execution.py +6 -4
  63. sky/global_user_state.py +22 -3
  64. sky/jobs/__init__.py +2 -0
  65. sky/jobs/client/sdk.py +67 -19
  66. sky/jobs/controller.py +2 -1
  67. sky/jobs/server/core.py +48 -1
  68. sky/jobs/server/server.py +52 -3
  69. sky/jobs/state.py +5 -1
  70. sky/schemas/db/global_user_state/002_add_workspace_to_cluster_history.py +35 -0
  71. sky/schemas/db/global_user_state/003_fix_initial_revision.py +61 -0
  72. sky/schemas/db/global_user_state/004_is_managed.py +34 -0
  73. sky/schemas/db/serve_state/001_initial_schema.py +67 -0
  74. sky/schemas/db/spot_jobs/003_pool_hash.py +34 -0
  75. sky/serve/client/impl.py +93 -6
  76. sky/serve/client/sdk.py +22 -53
  77. sky/serve/constants.py +2 -1
  78. sky/serve/controller.py +4 -2
  79. sky/serve/serve_state.py +444 -324
  80. sky/serve/serve_utils.py +77 -46
  81. sky/serve/server/core.py +13 -197
  82. sky/serve/server/impl.py +239 -2
  83. sky/serve/service.py +8 -3
  84. sky/server/common.py +18 -7
  85. sky/server/constants.py +1 -1
  86. sky/server/requests/executor.py +5 -3
  87. sky/server/requests/payloads.py +19 -0
  88. sky/setup_files/alembic.ini +4 -0
  89. sky/task.py +18 -11
  90. sky/templates/kubernetes-ray.yml.j2 +5 -0
  91. sky/templates/sky-serve-controller.yaml.j2 +1 -0
  92. sky/usage/usage_lib.py +8 -6
  93. sky/utils/annotations.py +8 -3
  94. sky/utils/cli_utils/status_utils.py +1 -1
  95. sky/utils/common_utils.py +11 -1
  96. sky/utils/db/db_utils.py +31 -0
  97. sky/utils/db/migration_utils.py +6 -2
  98. sky/utils/kubernetes/deploy_remote_cluster.py +3 -1
  99. sky/utils/resource_checker.py +162 -21
  100. sky/volumes/client/sdk.py +4 -4
  101. sky/workspaces/core.py +210 -6
  102. {skypilot_nightly-1.0.0.dev20250806.dist-info → skypilot_nightly-1.0.0.dev20250808.dist-info}/METADATA +19 -14
  103. {skypilot_nightly-1.0.0.dev20250806.dist-info → skypilot_nightly-1.0.0.dev20250808.dist-info}/RECORD +109 -103
  104. sky/client/sdk.pyi +0 -301
  105. sky/dashboard/out/_next/static/Gelsd19kVxXcX7aQQGsGu/_buildManifest.js +0 -1
  106. sky/dashboard/out/_next/static/chunks/1043-75af48ca5d5aaf57.js +0 -1
  107. sky/dashboard/out/_next/static/chunks/1141-8678a9102cc5f67e.js +0 -11
  108. sky/dashboard/out/_next/static/chunks/1664-22b00e32c9ff96a4.js +0 -1
  109. sky/dashboard/out/_next/static/chunks/1871-ced1c14230cad6e1.js +0 -6
  110. sky/dashboard/out/_next/static/chunks/2003.f90b06bb1f914295.js +0 -1
  111. sky/dashboard/out/_next/static/chunks/2350.fab69e61bac57b23.js +0 -1
  112. sky/dashboard/out/_next/static/chunks/2622-951867535095b0eb.js +0 -1
  113. sky/dashboard/out/_next/static/chunks/3785.0a173cd4393f0fef.js +0 -1
  114. sky/dashboard/out/_next/static/chunks/4725.42f21f250f91f65b.js +0 -1
  115. sky/dashboard/out/_next/static/chunks/4869.18e6a4361a380763.js +0 -16
  116. sky/dashboard/out/_next/static/chunks/5230-f3bb2663e442e86c.js +0 -1
  117. sky/dashboard/out/_next/static/chunks/6601-2109d22e7861861c.js +0 -1
  118. sky/dashboard/out/_next/static/chunks/6990-08b2a1cae076a943.js +0 -1
  119. sky/dashboard/out/_next/static/chunks/8969-9a8cca241b30db83.js +0 -1
  120. sky/dashboard/out/_next/static/chunks/9025.99f29acb7617963e.js +0 -6
  121. sky/dashboard/out/_next/static/chunks/938-bda2685db5eae6cf.js +0 -1
  122. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-7cb24da04ca00956.js +0 -11
  123. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-1e95993124dbfc57.js +0 -1
  124. sky/dashboard/out/_next/static/chunks/pages/config-d56e64f30db7b42e.js +0 -1
  125. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-90693cb88b5599a7.js +0 -11
  126. sky/dashboard/out/_next/static/chunks/pages/jobs-ab318e52eb4424a7.js +0 -1
  127. sky/dashboard/out/_next/static/chunks/pages/workspace/new-92f741084a89e27b.js +0 -1
  128. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-35e0de5bca55e594.js +0 -1
  129. sky/dashboard/out/_next/static/chunks/pages/workspaces-062525fb5462acb6.js +0 -1
  130. sky/dashboard/out/_next/static/chunks/webpack-387626669badf82e.js +0 -1
  131. sky/dashboard/out/_next/static/css/b3227360726f12eb.css +0 -3
  132. /sky/dashboard/out/_next/static/{Gelsd19kVxXcX7aQQGsGu → -DXZksWqf2waNHeU9YTQe}/_ssgManifest.js +0 -0
  133. /sky/dashboard/out/_next/static/chunks/{6135-2d7ed3350659d073.js → 6135-85426374db04811e.js} +0 -0
  134. {skypilot_nightly-1.0.0.dev20250806.dist-info → skypilot_nightly-1.0.0.dev20250808.dist-info}/WHEEL +0 -0
  135. {skypilot_nightly-1.0.0.dev20250806.dist-info → skypilot_nightly-1.0.0.dev20250808.dist-info}/entry_points.txt +0 -0
  136. {skypilot_nightly-1.0.0.dev20250806.dist-info → skypilot_nightly-1.0.0.dev20250808.dist-info}/licenses/LICENSE +0 -0
  137. {skypilot_nightly-1.0.0.dev20250806.dist-info → skypilot_nightly-1.0.0.dev20250808.dist-info}/top_level.txt +0 -0
sky/client/sdk.py CHANGED
@@ -16,7 +16,7 @@ import logging
16
16
  import os
17
17
  import subprocess
18
18
  import typing
19
- from typing import Any, Dict, List, Optional, Tuple, Union
19
+ from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
20
20
  from urllib import parse as urlparse
21
21
 
22
22
  import click
@@ -66,6 +66,10 @@ if typing.TYPE_CHECKING:
66
66
 
67
67
  import sky
68
68
  from sky import backends
69
+ from sky import models
70
+ import sky.catalog
71
+ from sky.provision.kubernetes import utils as kubernetes_utils
72
+ from sky.skylet import job_lib
69
73
  else:
70
74
  # only used in api_login()
71
75
  base64 = adaptors_common.LazyImport('base64')
@@ -82,16 +86,34 @@ logging.getLogger('httpx').setLevel(logging.CRITICAL)
82
86
 
83
87
  _LINE_PROCESSED_KEY = 'line_processed'
84
88
 
89
+ T = TypeVar('T')
90
+
85
91
 
86
92
  def reload_config() -> None:
87
93
  """Reloads the client-side config."""
88
94
  skypilot_config.safe_reload_config()
89
95
 
90
96
 
91
- def stream_response(request_id: Optional[server_common.RequestId],
97
+ @typing.overload
98
+ def stream_response(request_id: None,
99
+ response: 'requests.Response',
100
+ output_stream: Optional['io.TextIOBase'] = None,
101
+ resumable: bool = False) -> None:
102
+ ...
103
+
104
+
105
+ @typing.overload
106
+ def stream_response(request_id: server_common.RequestId[T],
107
+ response: 'requests.Response',
108
+ output_stream: Optional['io.TextIOBase'] = None,
109
+ resumable: bool = False) -> T:
110
+ ...
111
+
112
+
113
+ def stream_response(request_id: Optional[server_common.RequestId[T]],
92
114
  response: 'requests.Response',
93
115
  output_stream: Optional['io.TextIOBase'] = None,
94
- resumable: bool = False) -> Any:
116
+ resumable: bool = False) -> Optional[T]:
95
117
  """Streams the response to the console.
96
118
 
97
119
  Args:
@@ -121,6 +143,8 @@ def stream_response(request_id: Optional[server_common.RequestId],
121
143
  retry_context.line_processed = line_count
122
144
  if request_id is not None:
123
145
  return get(request_id)
146
+ else:
147
+ return None
124
148
  except Exception: # pylint: disable=broad-except
125
149
  logger.debug(f'To stream request logs: sky api logs {request_id}')
126
150
  raise
@@ -129,9 +153,11 @@ def stream_response(request_id: Optional[server_common.RequestId],
129
153
  @usage_lib.entrypoint
130
154
  @server_common.check_server_healthy_or_start
131
155
  @annotations.client_api
132
- def check(infra_list: Optional[Tuple[str, ...]],
133
- verbose: bool,
134
- workspace: Optional[str] = None) -> server_common.RequestId:
156
+ def check(
157
+ infra_list: Optional[Tuple[str, ...]],
158
+ verbose: bool,
159
+ workspace: Optional[str] = None
160
+ ) -> server_common.RequestId[Dict[str, List[str]]]:
135
161
  """Checks the credentials to enable clouds.
136
162
 
137
163
  Args:
@@ -174,7 +200,7 @@ def check(infra_list: Optional[Tuple[str, ...]],
174
200
  @server_common.check_server_healthy_or_start
175
201
  @annotations.client_api
176
202
  def enabled_clouds(workspace: Optional[str] = None,
177
- expand: bool = False) -> server_common.RequestId:
203
+ expand: bool = False) -> server_common.RequestId[List[str]]:
178
204
  """Gets the enabled clouds.
179
205
 
180
206
  Args:
@@ -198,14 +224,17 @@ def enabled_clouds(workspace: Optional[str] = None,
198
224
  @usage_lib.entrypoint
199
225
  @server_common.check_server_healthy_or_start
200
226
  @annotations.client_api
201
- def list_accelerators(gpus_only: bool = True,
202
- name_filter: Optional[str] = None,
203
- region_filter: Optional[str] = None,
204
- quantity_filter: Optional[int] = None,
205
- clouds: Optional[Union[List[str], str]] = None,
206
- all_regions: bool = False,
207
- require_price: bool = True,
208
- case_sensitive: bool = True) -> server_common.RequestId:
227
+ def list_accelerators(
228
+ gpus_only: bool = True,
229
+ name_filter: Optional[str] = None,
230
+ region_filter: Optional[str] = None,
231
+ quantity_filter: Optional[int] = None,
232
+ clouds: Optional[Union[List[str], str]] = None,
233
+ all_regions: bool = False,
234
+ require_price: bool = True,
235
+ case_sensitive: bool = True
236
+ ) -> server_common.RequestId[Dict[str,
237
+ List['sky.catalog.common.InstanceTypeInfo']]]:
209
238
  """Lists the names of all accelerators offered by Sky.
210
239
 
211
240
  This will include all accelerators offered by Sky, including those
@@ -248,12 +277,12 @@ def list_accelerators(gpus_only: bool = True,
248
277
  @server_common.check_server_healthy_or_start
249
278
  @annotations.client_api
250
279
  def list_accelerator_counts(
251
- gpus_only: bool = True,
252
- name_filter: Optional[str] = None,
253
- region_filter: Optional[str] = None,
254
- quantity_filter: Optional[int] = None,
255
- clouds: Optional[Union[List[str],
256
- str]] = None) -> server_common.RequestId:
280
+ gpus_only: bool = True,
281
+ name_filter: Optional[str] = None,
282
+ region_filter: Optional[str] = None,
283
+ quantity_filter: Optional[int] = None,
284
+ clouds: Optional[Union[List[str], str]] = None
285
+ ) -> server_common.RequestId[Dict[str, List[float]]]:
257
286
  """Lists all accelerators offered by Sky and available counts.
258
287
 
259
288
  Args:
@@ -292,7 +321,7 @@ def optimize(
292
321
  dag: 'sky.Dag',
293
322
  minimize: common.OptimizeTarget = common.OptimizeTarget.COST,
294
323
  admin_policy_request_options: Optional[admin_policy.RequestOptions] = None
295
- ) -> server_common.RequestId:
324
+ ) -> server_common.RequestId['sky.Dag']:
296
325
  """Finds the best execution plan for the given DAG.
297
326
 
298
327
  Args:
@@ -323,7 +352,7 @@ def optimize(
323
352
  return server_common.get_request_id(response)
324
353
 
325
354
 
326
- def workspaces() -> server_common.RequestId:
355
+ def workspaces() -> server_common.RequestId[Dict[str, Any]]:
327
356
  """Gets the workspaces."""
328
357
  response = server_common.make_authenticated_request('GET', '/workspaces')
329
358
  return server_common.get_request_id(response)
@@ -352,7 +381,14 @@ def validate(
352
381
  validation. This is only required when a admin policy is in use,
353
382
  see: https://docs.skypilot.co/en/latest/cloud-setup/policy.html
354
383
  """
384
+ remote_api_version = versions.get_remote_api_version()
385
+ # TODO(kevin): remove this in v0.13.0
386
+ omit_user_specified_yaml = (remote_api_version is None or
387
+ remote_api_version < 15)
355
388
  for task in dag.tasks:
389
+ if omit_user_specified_yaml:
390
+ # pylint: disable=protected-access
391
+ task._user_specified_yaml = None
356
392
  task.expand_and_validate_workdir()
357
393
  if not workdir_only:
358
394
  task.expand_and_validate_file_mounts()
@@ -402,7 +438,8 @@ def launch(
402
438
  _is_launched_by_jobs_controller: bool = False,
403
439
  _is_launched_by_sky_serve_controller: bool = False,
404
440
  _disable_controller_check: bool = False,
405
- ) -> server_common.RequestId:
441
+ ) -> server_common.RequestId[Tuple[Optional[int],
442
+ Optional['backends.ResourceHandle']]]:
406
443
  """Launches a cluster or task.
407
444
 
408
445
  The task's setup and run commands are executed under the task's workdir
@@ -584,7 +621,8 @@ def _launch(
584
621
  _is_launched_by_jobs_controller: bool = False,
585
622
  _is_launched_by_sky_serve_controller: bool = False,
586
623
  _disable_controller_check: bool = False,
587
- ) -> server_common.RequestId:
624
+ ) -> server_common.RequestId[Tuple[Optional[int],
625
+ Optional['backends.ResourceHandle']]]:
588
626
  """Auxiliary function for launch(), refer to launch() for details."""
589
627
 
590
628
  validate(dag, admin_policy_request_options=request_options)
@@ -597,8 +635,8 @@ def _launch(
597
635
  if _need_confirmation:
598
636
  cluster_status = None
599
637
  # TODO(SKY-998): we should reduce RTTs before launching the cluster.
600
- request_id = status([cluster_name], all_users=True)
601
- clusters = get(request_id)
638
+ status_request_id = status([cluster_name], all_users=True)
639
+ clusters = get(status_request_id)
602
640
  cluster_user_hash = common_utils.get_user_hash()
603
641
  cluster_user_hash_str = ''
604
642
  current_user = common_utils.get_current_user_name()
@@ -606,9 +644,9 @@ def _launch(
606
644
  if not clusters:
607
645
  # Show the optimize log before the prompt if the cluster does not
608
646
  # exist.
609
- request_id = optimize(dag,
610
- admin_policy_request_options=request_options)
611
- stream_and_get(request_id)
647
+ optimize_request_id = optimize(
648
+ dag, admin_policy_request_options=request_options)
649
+ stream_and_get(optimize_request_id)
612
650
  else:
613
651
  cluster_record = clusters[0]
614
652
  cluster_status = cluster_record['status']
@@ -683,7 +721,8 @@ def exec( # pylint: disable=redefined-builtin
683
721
  dryrun: bool = False,
684
722
  down: bool = False, # pylint: disable=redefined-outer-name
685
723
  backend: Optional['backends.Backend'] = None,
686
- ) -> server_common.RequestId:
724
+ ) -> server_common.RequestId[Tuple[Optional[int],
725
+ Optional['backends.ResourceHandle']]]:
687
726
  """Executes a task on an existing cluster.
688
727
 
689
728
  This function performs two actions:
@@ -805,7 +844,8 @@ def tail_logs(cluster_name: str,
805
844
  stream=True,
806
845
  timeout=(client_common.API_SERVER_REQUEST_CONNECTION_TIMEOUT_SECONDS,
807
846
  None))
808
- request_id = server_common.get_request_id(response)
847
+ request_id: server_common.RequestId[int] = server_common.get_request_id(
848
+ response)
809
849
  # Log request is idempotent when tail is 0, thus can resume previous
810
850
  # streaming point on retry.
811
851
  return stream_response(request_id=request_id,
@@ -847,8 +887,9 @@ def download_logs(cluster_name: str,
847
887
  )
848
888
  response = server_common.make_authenticated_request(
849
889
  'POST', '/download_logs', json=json.loads(body.model_dump_json()))
850
- job_id_remote_path_dict = stream_and_get(
851
- server_common.get_request_id(response))
890
+ request_id: server_common.RequestId[Dict[
891
+ str, str]] = server_common.get_request_id(response)
892
+ job_id_remote_path_dict = stream_and_get(request_id)
852
893
  remote2local_path_dict = client_common.download_logs_from_api_server(
853
894
  job_id_remote_path_dict.values())
854
895
  return {
@@ -867,7 +908,7 @@ def start(
867
908
  retry_until_up: bool = False,
868
909
  down: bool = False, # pylint: disable=redefined-outer-name
869
910
  force: bool = False,
870
- ) -> server_common.RequestId:
911
+ ) -> server_common.RequestId['backends.CloudVmRayResourceHandle']:
871
912
  """Restart a cluster.
872
913
 
873
914
  If a cluster is previously stopped (status is STOPPED) or failed in
@@ -949,7 +990,8 @@ def start(
949
990
  @usage_lib.entrypoint
950
991
  @server_common.check_server_healthy_or_start
951
992
  @annotations.client_api
952
- def down(cluster_name: str, purge: bool = False) -> server_common.RequestId:
993
+ def down(cluster_name: str,
994
+ purge: bool = False) -> server_common.RequestId[None]:
953
995
  """Tears down a cluster.
954
996
 
955
997
  Tearing down a cluster will delete all associated resources (all billing
@@ -991,7 +1033,8 @@ def down(cluster_name: str, purge: bool = False) -> server_common.RequestId:
991
1033
  @usage_lib.entrypoint
992
1034
  @server_common.check_server_healthy_or_start
993
1035
  @annotations.client_api
994
- def stop(cluster_name: str, purge: bool = False) -> server_common.RequestId:
1036
+ def stop(cluster_name: str,
1037
+ purge: bool = False) -> server_common.RequestId[None]:
995
1038
  """Stops a cluster.
996
1039
 
997
1040
  Data on attached disks is not lost when a cluster is stopped. Billing for
@@ -1041,7 +1084,7 @@ def autostop(
1041
1084
  idle_minutes: int,
1042
1085
  wait_for: Optional[autostop_lib.AutostopWaitFor] = None,
1043
1086
  down: bool = False, # pylint: disable=redefined-outer-name
1044
- ) -> server_common.RequestId:
1087
+ ) -> server_common.RequestId[None]:
1045
1088
  """Schedules an autostop/autodown for a cluster.
1046
1089
 
1047
1090
  Autostop/autodown will automatically stop or teardown a cluster when it
@@ -1121,7 +1164,7 @@ def autostop(
1121
1164
  @annotations.client_api
1122
1165
  def queue(cluster_name: str,
1123
1166
  skip_finished: bool = False,
1124
- all_users: bool = False) -> server_common.RequestId:
1167
+ all_users: bool = False) -> server_common.RequestId[List[dict]]:
1125
1168
  """Gets the job queue of a cluster.
1126
1169
 
1127
1170
  Args:
@@ -1178,8 +1221,11 @@ def queue(cluster_name: str,
1178
1221
  @usage_lib.entrypoint
1179
1222
  @server_common.check_server_healthy_or_start
1180
1223
  @annotations.client_api
1181
- def job_status(cluster_name: str,
1182
- job_ids: Optional[List[int]] = None) -> server_common.RequestId:
1224
+ def job_status(
1225
+ cluster_name: str,
1226
+ job_ids: Optional[List[int]] = None
1227
+ ) -> server_common.RequestId[Dict[Optional[int],
1228
+ Optional['job_lib.JobStatus']]]:
1183
1229
  """Gets the status of jobs on a cluster.
1184
1230
 
1185
1231
  Args:
@@ -1226,7 +1272,7 @@ def cancel(
1226
1272
  job_ids: Optional[List[int]] = None,
1227
1273
  # pylint: disable=invalid-name
1228
1274
  _try_cancel_if_cluster_is_init: bool = False
1229
- ) -> server_common.RequestId:
1275
+ ) -> server_common.RequestId[None]:
1230
1276
  """Cancels jobs on a cluster.
1231
1277
 
1232
1278
  Args:
@@ -1276,7 +1322,7 @@ def status(
1276
1322
  cluster_names: Optional[List[str]] = None,
1277
1323
  refresh: common.StatusRefreshMode = common.StatusRefreshMode.NONE,
1278
1324
  all_users: bool = False,
1279
- ) -> server_common.RequestId:
1325
+ ) -> server_common.RequestId[List[Dict[str, Any]]]:
1280
1326
  """Gets cluster statuses.
1281
1327
 
1282
1328
  If cluster_names is given, return those clusters. Otherwise, return all
@@ -1368,8 +1414,9 @@ def status(
1368
1414
  @server_common.check_server_healthy_or_start
1369
1415
  @annotations.client_api
1370
1416
  def endpoints(
1371
- cluster: str,
1372
- port: Optional[Union[int, str]] = None) -> server_common.RequestId:
1417
+ cluster: str,
1418
+ port: Optional[Union[int, str]] = None
1419
+ ) -> server_common.RequestId[Dict[str, str]]:
1373
1420
  """Gets the endpoint for a given cluster and port number (endpoint).
1374
1421
 
1375
1422
  Args:
@@ -1401,7 +1448,9 @@ def endpoints(
1401
1448
  @usage_lib.entrypoint
1402
1449
  @server_common.check_server_healthy_or_start
1403
1450
  @annotations.client_api
1404
- def cost_report(days: Optional[int] = None) -> server_common.RequestId: # pylint: disable=redefined-builtin
1451
+ def cost_report(
1452
+ days: Optional[int] = None
1453
+ ) -> server_common.RequestId[List[Dict[str, Any]]]: # pylint: disable=redefined-builtin
1405
1454
  """Gets all cluster cost reports, including those that have been downed.
1406
1455
 
1407
1456
  The estimated cost column indicates price for the cluster based on the type
@@ -1446,7 +1495,7 @@ def cost_report(days: Optional[int] = None) -> server_common.RequestId: # pylin
1446
1495
  @usage_lib.entrypoint
1447
1496
  @server_common.check_server_healthy_or_start
1448
1497
  @annotations.client_api
1449
- def storage_ls() -> server_common.RequestId:
1498
+ def storage_ls() -> server_common.RequestId[List[Dict[str, Any]]]:
1450
1499
  """Gets the storages.
1451
1500
 
1452
1501
  Returns:
@@ -1474,7 +1523,7 @@ def storage_ls() -> server_common.RequestId:
1474
1523
  @usage_lib.entrypoint
1475
1524
  @server_common.check_server_healthy_or_start
1476
1525
  @annotations.client_api
1477
- def storage_delete(name: str) -> server_common.RequestId:
1526
+ def storage_delete(name: str) -> server_common.RequestId[None]:
1478
1527
  """Deletes a storage.
1479
1528
 
1480
1529
  Args:
@@ -1507,7 +1556,7 @@ def local_up(gpus: bool,
1507
1556
  ssh_key: Optional[str],
1508
1557
  cleanup: bool,
1509
1558
  context_name: Optional[str] = None,
1510
- password: Optional[str] = None) -> server_common.RequestId:
1559
+ password: Optional[str] = None) -> server_common.RequestId[None]:
1511
1560
  """Launches a Kubernetes cluster on local machines.
1512
1561
 
1513
1562
  Returns:
@@ -1536,7 +1585,7 @@ def local_up(gpus: bool,
1536
1585
  @usage_lib.entrypoint
1537
1586
  @server_common.check_server_healthy_or_start
1538
1587
  @annotations.client_api
1539
- def local_down() -> server_common.RequestId:
1588
+ def local_down() -> server_common.RequestId[None]:
1540
1589
  """Tears down the Kubernetes cluster started by local_up."""
1541
1590
  # We do not allow local up when the API server is running remotely since it
1542
1591
  # will modify the kubeconfig.
@@ -1611,7 +1660,7 @@ def _upload_ssh_key_and_wait(key_name: str, key_file_path: str) -> str:
1611
1660
  @server_common.check_server_healthy_or_start
1612
1661
  @annotations.client_api
1613
1662
  def ssh_up(infra: Optional[str] = None,
1614
- file: Optional[str] = None) -> server_common.RequestId:
1663
+ file: Optional[str] = None) -> server_common.RequestId[None]:
1615
1664
  """Deploys the SSH Node Pools defined in ~/.sky/ssh_targets.yaml.
1616
1665
 
1617
1666
  Args:
@@ -1644,7 +1693,7 @@ def ssh_up(infra: Optional[str] = None,
1644
1693
  @usage_lib.entrypoint
1645
1694
  @server_common.check_server_healthy_or_start
1646
1695
  @annotations.client_api
1647
- def ssh_down(infra: Optional[str] = None) -> server_common.RequestId:
1696
+ def ssh_down(infra: Optional[str] = None) -> server_common.RequestId[None]:
1648
1697
  """Tears down a Kubernetes cluster on SSH targets.
1649
1698
 
1650
1699
  Args:
@@ -1673,10 +1722,12 @@ def ssh_down(infra: Optional[str] = None) -> server_common.RequestId:
1673
1722
  @server_common.check_server_healthy_or_start
1674
1723
  @annotations.client_api
1675
1724
  def realtime_kubernetes_gpu_availability(
1676
- context: Optional[str] = None,
1677
- name_filter: Optional[str] = None,
1678
- quantity_filter: Optional[int] = None,
1679
- is_ssh: Optional[bool] = None) -> server_common.RequestId:
1725
+ context: Optional[str] = None,
1726
+ name_filter: Optional[str] = None,
1727
+ quantity_filter: Optional[int] = None,
1728
+ is_ssh: Optional[bool] = None
1729
+ ) -> server_common.RequestId[List[Tuple[
1730
+ str, List['models.RealtimeGpuAvailability']]]]:
1680
1731
  """Gets the real-time Kubernetes GPU availability.
1681
1732
 
1682
1733
  Returns:
@@ -1699,7 +1750,8 @@ def realtime_kubernetes_gpu_availability(
1699
1750
  @server_common.check_server_healthy_or_start
1700
1751
  @annotations.client_api
1701
1752
  def kubernetes_node_info(
1702
- context: Optional[str] = None) -> server_common.RequestId:
1753
+ context: Optional[str] = None
1754
+ ) -> server_common.RequestId['models.KubernetesNodesInfo']:
1703
1755
  """Gets the resource information for all the nodes in the cluster.
1704
1756
 
1705
1757
  Currently only GPU resources are supported. The function returns the total
@@ -1730,7 +1782,10 @@ def kubernetes_node_info(
1730
1782
  @usage_lib.entrypoint
1731
1783
  @server_common.check_server_healthy_or_start
1732
1784
  @annotations.client_api
1733
- def status_kubernetes() -> server_common.RequestId:
1785
+ def status_kubernetes() -> server_common.RequestId[Tuple[
1786
+ List['kubernetes_utils.KubernetesSkyPilotClusterInfoPayload'],
1787
+ List['kubernetes_utils.KubernetesSkyPilotClusterInfoPayload'], List[Dict[
1788
+ str, Any]], Optional[str]]]:
1734
1789
  """Gets all SkyPilot clusters and jobs in the Kubernetes cluster.
1735
1790
 
1736
1791
  Managed jobs and services are also included in the clusters returned.
@@ -1759,7 +1814,7 @@ def status_kubernetes() -> server_common.RequestId:
1759
1814
  # === API request APIs ===
1760
1815
  @usage_lib.entrypoint
1761
1816
  @annotations.client_api
1762
- def get(request_id: server_common.RequestId) -> Any:
1817
+ def get(request_id: server_common.RequestId[T]) -> T:
1763
1818
  """Waits for and gets the result of a request.
1764
1819
 
1765
1820
  This function will not check the server health since /api/get is typically
@@ -1818,16 +1873,34 @@ def get(request_id: server_common.RequestId) -> Any:
1818
1873
  return request_task.get_return_value()
1819
1874
 
1820
1875
 
1876
+ @typing.overload
1877
+ def stream_and_get(request_id: server_common.RequestId[T],
1878
+ log_path: Optional[str] = None,
1879
+ tail: Optional[int] = None,
1880
+ follow: bool = True,
1881
+ output_stream: Optional['io.TextIOBase'] = None) -> T:
1882
+ ...
1883
+
1884
+
1885
+ @typing.overload
1886
+ def stream_and_get(request_id: None = None,
1887
+ log_path: Optional[str] = None,
1888
+ tail: Optional[int] = None,
1889
+ follow: bool = True,
1890
+ output_stream: Optional['io.TextIOBase'] = None) -> None:
1891
+ ...
1892
+
1893
+
1821
1894
  @usage_lib.entrypoint
1822
1895
  @server_common.check_server_healthy_or_start
1823
1896
  @annotations.client_api
1824
1897
  def stream_and_get(
1825
- request_id: Optional[server_common.RequestId] = None,
1898
+ request_id: Optional[server_common.RequestId[T]] = None,
1826
1899
  log_path: Optional[str] = None,
1827
1900
  tail: Optional[int] = None,
1828
1901
  follow: bool = True,
1829
1902
  output_stream: Optional['io.TextIOBase'] = None,
1830
- ) -> Any:
1903
+ ) -> Optional[T]:
1831
1904
  """Streams the logs of a request or a log file and gets the final result.
1832
1905
 
1833
1906
  This will block until the request is finished. The request id can be a
@@ -1874,16 +1947,21 @@ def stream_and_get(
1874
1947
  with ux_utils.print_exception_no_traceback():
1875
1948
  raise RuntimeError(f'Failed to stream logs: {detail}')
1876
1949
  elif response.status_code != 200:
1950
+ # TODO(syang): handle the case where the requestID is not provided
1951
+ # see https://github.com/skypilot-org/skypilot/issues/6549
1952
+ if request_id is None:
1953
+ return None
1877
1954
  return get(request_id)
1878
1955
  return stream_response(request_id, response, output_stream)
1879
1956
 
1880
1957
 
1881
1958
  @usage_lib.entrypoint
1882
1959
  @annotations.client_api
1883
- def api_cancel(request_ids: Optional[Union[
1884
- server_common.RequestId, List[server_common.RequestId]]] = None,
1960
+ def api_cancel(request_ids: Optional[Union[server_common.RequestId[T],
1961
+ List[server_common.RequestId[T]],
1962
+ str, List[str]]] = None,
1885
1963
  all_users: bool = False,
1886
- silent: bool = False) -> server_common.RequestId:
1964
+ silent: bool = False) -> server_common.RequestId[List[str]]:
1887
1965
  """Aborts a request or all requests.
1888
1966
 
1889
1967
  Args:
@@ -1946,7 +2024,7 @@ def _local_api_server_running(kill: bool = False) -> bool:
1946
2024
  @usage_lib.entrypoint
1947
2025
  @annotations.client_api
1948
2026
  def api_status(
1949
- request_ids: Optional[List[server_common.RequestId]] = None,
2027
+ request_ids: Optional[List[Union[server_common.RequestId[T], str]]] = None,
1950
2028
  # pylint: disable=redefined-builtin
1951
2029
  all_status: bool = False
1952
2030
  ) -> List[payloads.RequestPayload]:
@@ -2420,7 +2498,9 @@ def api_login(endpoint: Optional[str] = None,
2420
2498
  _save_config_updates(endpoint=endpoint)
2421
2499
  dashboard_url = server_common.get_dashboard_url(endpoint)
2422
2500
 
2423
- server_common.get_api_server_status.cache_clear()
2501
+ # see https://github.com/python/mypy/issues/5107 on why
2502
+ # typing is disabled on this line
2503
+ server_common.get_api_server_status.cache_clear() # type: ignore
2424
2504
  # After successful authentication, check server health again to get user
2425
2505
  # identity
2426
2506
  server_status, final_api_server_info = server_common.check_server_healthy(
sky/client/sdk_async.py CHANGED
@@ -244,6 +244,10 @@ async def stream_and_get(
244
244
  with ux_utils.print_exception_no_traceback():
245
245
  raise RuntimeError(f'Failed to stream logs: {detail}')
246
246
  elif response.status != 200:
247
+ # TODO(syang): handle the case where the requestID is not
248
+ # provided. https://github.com/skypilot-org/skypilot/issues/6549
249
+ if request_id is None:
250
+ return None
247
251
  return await get(request_id)
248
252
 
249
253
  return await stream_response_async(request_id, response,
@@ -583,7 +587,7 @@ async def endpoints(
583
587
  cluster: str,
584
588
  port: Optional[Union[int, str]] = None,
585
589
  stream_logs: Optional[StreamConfig] = DEFAULT_STREAM_CONFIG
586
- ) -> Dict[int, str]:
590
+ ) -> Dict[str, str]:
587
591
  """Async version of endpoints() that gets the endpoint for a given cluster
588
592
  and port number."""
589
593
  request_id = await context_utils.to_thread(sdk.endpoints, cluster, port)
sky/core.py CHANGED
@@ -256,7 +256,7 @@ all_clusters, unmanaged_clusters, all_jobs, context
256
256
 
257
257
 
258
258
  def endpoints(cluster: str,
259
- port: Optional[Union[int, str]] = None) -> Dict[int, str]:
259
+ port: Optional[Union[int, str]] = None) -> Dict[str, str]:
260
260
  """Gets the endpoint for a given cluster and port number (endpoint).
261
261
 
262
262
  Args:
@@ -275,7 +275,10 @@ def endpoints(cluster: str,
275
275
  with rich_utils.safe_status(
276
276
  ux_utils.spinner_message(
277
277
  f'Fetching endpoints for cluster {cluster}')):
278
- return backend_utils.get_endpoints(cluster=cluster, port=port)
278
+ result = backend_utils.get_endpoints(cluster=cluster, port=port)
279
+ # Convert int keys to str keys.
280
+ # In JSON serialization, each key must be a string.
281
+ return {str(k): v for k, v in result.items()}
279
282
 
280
283
 
281
284
  @usage_lib.entrypoint
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/b3227360726f12eb.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/b3227360726f12eb.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-387626669badf82e.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-cf60a09ccd051a10.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-f15ccb73239a3bf1.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-2a43ea3241bbdacd.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js" defer=""></script><script src="/dashboard/_next/static/Gelsd19kVxXcX7aQQGsGu/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/Gelsd19kVxXcX7aQQGsGu/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"Gelsd19kVxXcX7aQQGsGu","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/4614e06482d7309e.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/4614e06482d7309e.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-339efec49c0cc7d0.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-cf60a09ccd051a10.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-f15ccb73239a3bf1.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-491a4d699d95e808.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js" defer=""></script><script src="/dashboard/_next/static/-DXZksWqf2waNHeU9YTQe/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/-DXZksWqf2waNHeU9YTQe/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"-DXZksWqf2waNHeU9YTQe","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
@@ -0,0 +1 @@
1
+ self.__BUILD_MANIFEST=function(s,c,e,a,t,f,u,n,o,r,j,i,b,k,h){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-444f1804401f04ea.js"],"/_error":["static/chunks/pages/_error-c66a4e8afc46f17b.js"],"/clusters":["static/chunks/pages/clusters-b30460f683e6ba96.js"],"/clusters/[cluster]":[s,c,e,f,u,j,r,a,t,n,i,b,o,k,h,"static/chunks/1871-980a395e92633a5c.js","static/chunks/pages/clusters/[cluster]-155d477a6c3e04e2.js"],"/clusters/[cluster]/[job]":[s,c,e,f,a,t,o,"static/chunks/pages/clusters/[cluster]/[job]-ae17cec0fc6483d9.js"],"/config":["static/chunks/pages/config-dfb9bf07b13045f4.js"],"/infra":["static/chunks/pages/infra-fc9222e26c8e2f0d.js"],"/infra/[context]":["static/chunks/pages/infra/[context]-13d53fffc03ccb52.js"],"/jobs":["static/chunks/pages/jobs-cdc60fb5d371e16a.js"],"/jobs/pools/[pool]":[s,c,e,u,r,a,t,n,"static/chunks/pages/jobs/pools/[pool]-f5ccf5d39d87aebe.js"],"/jobs/[job]":[s,c,e,f,u,r,a,t,n,o,"static/chunks/pages/jobs/[job]-154f55cf8af55be5.js"],"/users":["static/chunks/pages/users-7ed36e44e779d5c7.js"],"/volumes":["static/chunks/pages/volumes-c9695d657f78b5dc.js"],"/workspace/new":["static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js"],"/workspaces":["static/chunks/pages/workspaces-8f67be60165724cc.js"],"/workspaces/[name]":[s,c,e,f,u,j,a,t,n,i,b,o,k,h,"static/chunks/1141-a8a8f1adba34c892.js","static/chunks/pages/workspaces/[name]-f72f73bcef9541dc.js"],sortedPages:["/","/_app","/_error","/clusters","/clusters/[cluster]","/clusters/[cluster]/[job]","/config","/infra","/infra/[context]","/jobs","/jobs/pools/[pool]","/jobs/[job]","/users","/volumes","/workspace/new","/workspaces","/workspaces/[name]"]}}("static/chunks/616-3d59f75e2ccf9321.js","static/chunks/6130-2be46d70a38f1e82.js","static/chunks/5739-d67458fcb1386c92.js","static/chunks/6989-6129c1cfbcf51063.js","static/chunks/3850-ff4a9a69d978632b.js","static/chunks/7411-b15471acd2cba716.js","static/chunks/1272-1ef0bf0237faccdb.js","static/chunks/8969-c9686994ddafcf01.js","static/chunks/6135-85426374db04811e.js","static/chunks/6212-7bd06f60ba693125.js","static/chunks/1559-6c00e20454194859.js","static/chunks/6990-0f886f16e0d55ff8.js","static/chunks/8056-34d27f51e6d1c631.js","static/chunks/6601-06114c982db410b6.js","static/chunks/9159-11421c0f2909236f.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -0,0 +1,11 @@
1
+ "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1141],{99333:function(e,s,r){r.d(s,{Z:function(){return t}});/**
2
+ * @license lucide-react v0.407.0 - ISC
3
+ *
4
+ * This source code is licensed under the ISC license.
5
+ * See the LICENSE file in the root directory of this source tree.
6
+ */let t=(0,r(60998).Z)("Save",[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",key:"1c8476"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7",key:"1ydtos"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7",key:"t51u73"}]])},98418:function(e,s,r){r.d(s,{Z:function(){return t}});/**
7
+ * @license lucide-react v0.407.0 - ISC
8
+ *
9
+ * This source code is licensed under the ISC license.
10
+ * See the LICENSE file in the root directory of this source tree.
11
+ */let t=(0,r(60998).Z)("Trash",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}]])},1812:function(e,s,r){r.d(s,{X:function(){return n}});var t=r(85893),a=r(67294);let l=e=>{if(!(null==e?void 0:e.message))return"An unexpected error occurred.";let s=e.message;return s.includes("failed:")&&(s=s.split("failed:")[1].trim()),s},n=e=>{let{error:s,title:r="Error",onDismiss:n}=e,[c,i]=(0,a.useState)(!1);if((0,a.useEffect)(()=>{s&&i(!1)},[s]),!s||c)return null;let o="string"==typeof s?s:l(s);return(0,t.jsx)("div",{className:"bg-red-50 border border-red-200 rounded-md p-3 mb-4",children:(0,t.jsxs)("div",{className:"flex items-center justify-between",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("div",{className:"flex-shrink-0",children:(0,t.jsx)("svg",{className:"h-5 w-5 text-red-400",viewBox:"0 0 20 20",fill:"currentColor",children:(0,t.jsx)("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z",clipRule:"evenodd"})})}),(0,t.jsx)("div",{className:"ml-3",children:(0,t.jsxs)("div",{className:"text-sm text-red-800",children:[(0,t.jsxs)("strong",{children:[r,":"]})," ",o]})})]}),(0,t.jsx)("button",{onClick:()=>{i(!0),n&&n()},className:"flex-shrink-0 ml-4 text-red-400 hover:text-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:ring-offset-red-50 rounded","aria-label":"Dismiss error",children:(0,t.jsx)("svg",{className:"h-4 w-4",viewBox:"0 0 20 20",fill:"currentColor",children:(0,t.jsx)("path",{fillRule:"evenodd",d:"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z",clipRule:"evenodd"})})})]})})}},69123:function(e,s,r){r.d(s,{g:function(){return n}});var t=r(85893),a=r(67294),l=r(32350);let n=a.forwardRef((e,s)=>{let{className:r,...a}=e;return(0,t.jsx)("textarea",{className:(0,l.cn)("flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",r),ref:s,...a})});n.displayName="Textarea"},11141:function(e,s,r){r.r(s),r.d(s,{WorkspaceEditor:function(){return R}});var t=r(85893),a=r(67294),l=r(11163),n=r(17324),c=r(23266),i=r(68969);r(6135);var o=r(41664),d=r.n(o),u=r(9008),x=r.n(u),m=r(37673),h=r(30803),f=r(69123),g=r(55739),p=r(70282),b=r(6021),j=r(13626),y=r(98418),N=r(99333),v=r(50326);let w=e=>{let{className:s="",variant:r="default",children:a,...l}=e;return(0,t.jsx)("div",{role:"alert",className:"".concat("relative w-full rounded-lg border p-4 flex items-start space-x-2"," ").concat({default:"bg-blue-50 border-blue-200 text-blue-800",destructive:"bg-red-50 border-red-200 text-red-800"}[r]," ").concat(s),...l,children:a})},k=e=>{let{className:s="",children:r,...a}=e;return(0,t.jsx)("div",{className:"text-sm leading-relaxed ".concat(s),...a,children:r})};var C=r(53850),L=r(1812),S=r(9159),E=r(1272),A=r(93225),W=r(53081);let Z=e=>{let{message:s}=e;return s?(0,t.jsxs)(w,{className:"border-green-200 bg-green-50",children:[(0,t.jsx)(p.Z,{className:"h-4 w-4 text-green-600"}),(0,t.jsx)(k,{className:"text-green-800",children:s})]}):null},D=e=>{let{workspaceName:s,config:r,enabledClouds:a=[]}=e;if(!r)return null;let l="default"===s,n=0===Object.keys(r).length;if(l&&n)return(0,t.jsx)("div",{className:"text-sm text-gray-500 mb-3 italic p-3 bg-sky-50 rounded border border-sky-200",children:"Workspace 'default' can use all accessible infrastructure."});let c=[],i=[],o=[],d=new Set(a.map(e=>e.toLowerCase()));Object.entries(r).forEach(e=>{let[s,r]=e;if("private"===s||"allowed_users"===s)return;let a=A.Z2[s.toLowerCase()]||s.toUpperCase(),l=null==a?void 0:a.toLowerCase(),n=d.has(l)||Array.from(d).some(e=>e.startsWith(l+"/")),u=()=>"kubernetes"===s.toLowerCase()?Array.from(d).filter(e=>e.startsWith(l+"/")).map(e=>e.split("/")[1]):[];if((null==r?void 0:r.disabled)===!0)i.push(a);else if(r&&Object.keys(r).length>0){let e="";if("gcp"===s.toLowerCase()&&r.project_id)e=" (Project ID: ".concat(r.project_id,")");else if("aws"===s.toLowerCase()&&r.region)e=" (Region: ".concat(r.region,")");else if("kubernetes"===s.toLowerCase()){let s=u();s.length>0&&(e=" (Contexts: ".concat(s.join(", "),")"))}n?c.push((0,t.jsxs)("span",{className:"block",children:[a,e," is enabled."]},"".concat(s,"-enabled"))):o.push((0,t.jsxs)("span",{className:"block text-amber-700",children:[a,e," is configured but not currently available."]},"".concat(s,"-configured-not-enabled")))}else if(n){let e="";if("kubernetes"===s.toLowerCase()){let s=u();s.length>0&&(e=" (Contexts: ".concat(s.join(", "),")"))}c.push((0,t.jsxs)("span",{className:"block",children:[a,e," is enabled (using default settings)."]},"".concat(s,"-default-enabled")))}else o.push((0,t.jsxs)("span",{className:"block text-amber-700",children:[a," is configured but not currently available."]},"".concat(s,"-default-not-enabled")))});let u=[];if(i.length>0){let e=i.join(" and ");u.push((0,t.jsxs)("span",{className:"block",children:[e," ",1===i.length?"is":"are"," explicitly disabled."]},"disabled-clouds"))}return(u.push(...c),u.push(...o),u.length>0)?(0,t.jsx)("div",{className:"text-sm text-gray-700 mb-3 p-3 bg-sky-50 rounded border border-sky-200",children:u}):!l&&n?(0,t.jsx)("div",{className:"text-sm text-gray-500 mb-3 italic p-3 bg-sky-50 rounded border border-sky-200",children:"This workspace has no specific cloud resource configurations and can use all accessible infrastructure."}):null},M=e=>{let{isPrivate:s}=e;return s?(0,t.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-700 border border-gray-300",children:"Private"}):(0,t.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-700 border border-green-300",children:"Public"})},P=e=>{let{workspaceConfig:s,allUsers:r}=e;if(!s.private)return null;let a=s.allowed_users||[],l=(r||[]).filter(e=>"admin"===e.role).map(e=>e.username),n=[...new Set([...a,...l])];return 0===n.length?(0,t.jsxs)("div",{className:"mt-4",children:[(0,t.jsx)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:"Allowed Users (0)"}),(0,t.jsx)("div",{className:"text-amber-600 text-xs italic p-2 bg-amber-50 rounded border border-amber-200",children:"No users configured (workspace may be inaccessible)"})]}):(0,t.jsxs)("div",{className:"mt-4",children:[(0,t.jsxs)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:["Allowed Users (",n.length,")"]}),(0,t.jsx)("div",{className:"space-y-1 max-h-48 overflow-y-auto border border-gray-200 rounded",children:n.map(e=>{let s=l.includes(e);return(0,t.jsxs)("div",{className:"flex items-center justify-between text-xs p-2 bg-gray-50 hover:bg-gray-100 border-b border-gray-100 last:border-b-0",children:[(0,t.jsx)("span",{className:"font-medium text-gray-700",children:e}),s?(0,t.jsxs)("span",{className:"inline-flex items-center text-blue-600",children:[(0,t.jsx)(C.r7,{className:"w-3 h-3 mr-1"}),"Admin"]}):(0,t.jsxs)("span",{className:"inline-flex items-center text-gray-600",children:[(0,t.jsx)(b.Z,{className:"w-3 h-3 mr-1"}),"User"]})]},e)})})]})};function R(e){let{workspaceName:s,isNewWorkspace:r=!1}=e,o=(0,l.useRouter)(),[u,p]=(0,a.useState)({}),[b,w]=(0,a.useState)({}),[k,A]=(0,a.useState)(""),[R,_]=(0,a.useState)(!0),[z,O]=(0,a.useState)(!1),[T,U]=(0,a.useState)(!1),[Y,J]=(0,a.useState)(null),[I,V]=(0,a.useState)(null),[F,H]=(0,a.useState)(null),[X,B]=(0,a.useState)([]),[G,q]=(0,a.useState)({showDialog:!1,deleting:!1,error:null}),[K,Q]=(0,a.useState)({totalClusterCount:0,runningClusterCount:0,managedJobsCount:0,clouds:[]}),[$,ee]=(0,a.useState)(!1),es=(0,a.useCallback)(async()=>{_(!0),J(null);try{let e;let[r,t]=await Promise.all([(0,n.fX)(),(0,W.R)()]),a=r[s]||{};p(a),w(a),B(t||[]),e=0===Object.keys(a).length?"".concat(s,":\n # Empty workspace configuration - uses all accessible infrastructure\n"):E.ZP.dump({[s]:a},{indent:2,lineWidth:-1,noRefs:!0,skipInvalid:!0,flowLevel:-1}),A(e)}catch(e){console.error("Error fetching workspace config:",e),J(e)}finally{_(!1)}},[s]),er=(0,a.useCallback)(async()=>{if(!r){ee(!0);try{let[e,r,t]=await Promise.all([(0,c.getClusters)(),(0,i.getManagedJobs)(),(0,n.yz)(s,!0)]),a=e.filter(e=>(e.workspace||"default")===s),l=a.filter(e=>"RUNNING"===e.status||"LAUNCHING"===e.status),o={};e.forEach(e=>{o[e.cluster]=e.workspace||"default"});let d=r.jobs||[],u=new Set(S.statusGroups.active),x=0;d.forEach(e=>{let r=e.cluster_name||e.resources&&e.resources.cluster_name;r&&o[r]===s&&u.has(e.status)&&x++}),Q({totalClusterCount:a.length,runningClusterCount:l.length,managedJobsCount:x,clouds:Array.isArray(t)?t:[]})}catch(e){console.error("Failed to fetch workspace stats:",e)}finally{ee(!1)}}},[s,r]);(0,a.useEffect)(()=>{r?(_(!1),A("".concat(s,":\n # New workspace configuration\n # Leave empty to use all accessible infrastructure\n"))):(es(),er())},[s,r,es,er]),(0,a.useEffect)(()=>{U(JSON.stringify(u)!==JSON.stringify(b))},[u,b]);let et=e=>{A(e),H(null);try{let r=E.ZP.load(e)||{},t=Object.keys(r);if(0===t.length)p({});else if(1===t.length){let e=t[0];if(e!==s){H('Workspace name cannot be changed. Expected "'.concat(s,'" but found "').concat(e,'".'));return}let a=r[s]||{};p(a)}else H("Configuration must contain only one workspace. Found: ".concat(t.join(", ")))}catch(e){H("Invalid YAML: ".concat(e.message))}},ea=async()=>{O(!0),J(null),V(null);try{if(F)throw Error("Please fix YAML errors before saving");let e=E.ZP.load(k)||{},t=Object.keys(e);if(t.length>0&&t[0]!==s)throw Error('Workspace name cannot be changed. Expected "'.concat(s,'".'));r?(await (0,n.MB)(s,u),V("Workspace created successfully!"),setTimeout(()=>{o.push("/workspaces/".concat(s))},1500)):(await (0,n.eA)(s,u),V("Workspace updated successfully!"),w(u),er())}catch(e){console.error("Error saving workspace:",e),J(e)}finally{O(!1)}},el=async()=>{q(e=>({...e,deleting:!0,error:null}));try{await (0,n.zl)(s),V("Workspace deleted successfully!"),setTimeout(()=>{o.push("/workspaces")},1500)}catch(e){console.error("Error deleting workspace:",e),q(s=>({...s,deleting:!1,error:e}))}},en=()=>{q({showDialog:!1,deleting:!1,error:null})},ec=async()=>{await Promise.all([es(),er()])};if(!o.isReady)return(0,t.jsx)("div",{children:"Loading..."});let ei=r?"Create New Workspace | SkyPilot Dashboard":"Workspace: ".concat(s," | SkyPilot Dashboard");return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(x(),{children:(0,t.jsx)("title",{children:ei})}),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-4 h-5",children:[(0,t.jsxs)("div",{className:"text-base flex items-center",children:[(0,t.jsx)(d(),{href:"/workspaces",className:"text-sky-blue hover:underline",children:"Workspaces"}),(0,t.jsx)("span",{className:"mx-2 text-gray-500",children:"›"}),(0,t.jsx)(d(),{href:r?"/workspace/new":"/workspaces/".concat(s),className:"text-sky-blue hover:underline",children:r?"New Workspace":s}),T&&(0,t.jsx)("span",{className:"ml-3 px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded",children:"Unsaved changes"})]}),(0,t.jsxs)("div",{className:"text-sm flex items-center",children:[(R||z||$)&&(0,t.jsxs)("div",{className:"flex items-center mr-4",children:[(0,t.jsx)(g.Z,{size:15,className:"mt-0"}),(0,t.jsx)("span",{className:"ml-2 text-gray-500",children:z?"Saving...":"Loading..."})]}),(0,t.jsxs)("div",{className:"flex items-center space-x-4",children:[!r&&(0,t.jsxs)("button",{onClick:ec,disabled:R||z||$,className:"text-sky-blue hover:text-sky-blue-bright font-medium inline-flex items-center",children:[(0,t.jsx)(j.Z,{className:"w-4 h-4 mr-1.5"}),"Refresh"]}),!r&&"default"!==s&&(0,t.jsxs)("button",{onClick:()=>q({...G,showDialog:!0}),disabled:G.deleting||z,className:"text-red-600 hover:text-red-700 font-medium inline-flex items-center",children:[(0,t.jsx)(y.Z,{className:"w-4 h-4 mr-1.5"}),"Delete"]})]})]})]}),R?(0,t.jsxs)("div",{className:"flex justify-center items-center py-12",children:[(0,t.jsx)(g.Z,{size:24,className:"mr-2"}),(0,t.jsx)("span",{className:"text-gray-500",children:"Loading workspace configuration..."})]}):(0,t.jsxs)("div",{className:"space-y-6",children:[(0,t.jsx)(L.X,{error:Y,title:"Error",onDismiss:()=>J(null)}),(0,t.jsx)(Z,{message:I}),(0,t.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-6",children:[!r&&(0,t.jsx)("div",{className:"lg:col-span-1",children:(0,t.jsxs)(m.Zb,{className:"h-full",children:[(0,t.jsx)(m.Ol,{children:(0,t.jsx)(m.ll,{className:"text-base font-normal",children:(0,t.jsxs)("div",{className:"flex items-center justify-between",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"font-semibold",children:"Workspace:"})," ",s]}),(0,t.jsx)(M,{isPrivate:!0===b.private})]})})}),(0,t.jsxs)(m.aY,{className:"text-sm pb-2 flex-1",children:[(0,t.jsxs)("div",{className:"py-2 flex items-center justify-between",children:[(0,t.jsxs)("div",{className:"flex items-center text-gray-600",children:[(0,t.jsx)(C.QT,{className:"w-4 h-4 mr-2 text-gray-500"}),(0,t.jsx)("span",{children:"Clusters (Running / Total)"})]}),(0,t.jsx)("span",{className:"font-normal text-gray-800",children:$?"...":"".concat(K.runningClusterCount," / ").concat(K.totalClusterCount)})]}),(0,t.jsxs)("div",{className:"py-2 flex items-center justify-between border-t border-gray-100",children:[(0,t.jsxs)("div",{className:"flex items-center text-gray-600",children:[(0,t.jsx)(C.Vp,{className:"w-4 h-4 mr-2 text-gray-500"}),(0,t.jsx)("span",{children:"Managed Jobs"})]}),(0,t.jsx)("span",{className:"font-normal text-gray-800",children:$?"...":K.managedJobsCount})]})]}),(0,t.jsxs)("div",{className:"px-6 pb-6 text-sm pt-3",children:[(0,t.jsx)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:"Enabled Infra"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:$?(0,t.jsx)("span",{className:"text-gray-500",children:"Loading..."}):K.clouds.length>0?K.clouds.map(e=>(0,t.jsxs)("div",{className:"flex items-center text-gray-700",children:[(0,t.jsx)(C.Ye,{className:"w-3.5 h-3.5 mr-1.5 text-green-500"}),(0,t.jsx)("span",{children:e})]},e)):(0,t.jsx)("span",{className:"text-gray-500 italic",children:"No enabled infrastructure"})}),(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsx)(D,{workspaceName:s,config:b,enabledClouds:K.clouds})}),(0,t.jsx)(P,{workspaceConfig:b,allUsers:X})]})]})}),(0,t.jsx)("div",{className:r?"lg:col-span-3":"lg:col-span-2",children:(0,t.jsxs)(m.Zb,{className:"h-full flex flex-col",children:[(0,t.jsx)(m.Ol,{children:(0,t.jsx)(m.ll,{className:"text-base font-normal",children:r?"New Workspace YAML":"Edit Workspace YAML"})}),(0,t.jsx)(m.aY,{className:"flex-1 flex flex-col",children:(0,t.jsxs)("div",{className:"space-y-4 flex-1 flex flex-col",children:[F&&(0,t.jsx)(L.X,{error:F,onDismiss:()=>H(null)}),(0,t.jsxs)("div",{className:"flex-1 flex flex-col",children:[(0,t.jsxs)("p",{className:"text-sm text-gray-600 mb-3",children:["Configure infra-specific settings for this workspace. Leave empty to use all accessible infrastructure. Refer to"," ",(0,t.jsx)("a",{href:"https://docs.skypilot.co/en/latest/admin/workspaces.html#configuration",target:"_blank",rel:"noopener noreferrer",className:"text-blue-600",children:"SkyPilot Docs"})," ","for more details."]}),(0,t.jsxs)("div",{className:"mb-4",children:[(0,t.jsx)("h4",{className:"text-sm font-medium text-gray-700 mb-2",children:"Example configuration:"}),(0,t.jsx)("div",{className:"p-3 bg-gray-50 border rounded-lg",children:(0,t.jsx)("pre",{className:"text-xs font-mono text-gray-600 whitespace-pre-wrap",children:"".concat(s||"my-workspace",":\n private: true\n allowed_users:\n - user1@mydomain.com\n - user2@mydomain.com\n gcp:\n project_id: xxx\n disabled: false\n kubernetes:\n allowed_contexts:\n - context-1")})})]}),(0,t.jsx)(f.g,{value:k,onChange:e=>et(e.target.value),className:"font-mono text-sm flex-1 resize-none",style:{minHeight:"350px"},spellCheck:!1,placeholder:"# Enter workspace configuration in YAML format"}),(0,t.jsx)("div",{className:"flex justify-end space-x-3 pt-3 border-gray-200",children:(0,t.jsxs)(h.z,{onClick:ea,disabled:z||F||R,className:"inline-flex items-center bg-sky-600 hover:bg-sky-700 text-white",children:[(0,t.jsx)(N.Z,{className:"w-4 h-4 mr-1.5"}),z?"Applying...":"Apply"]})})]})]})})]})})]})]}),(0,t.jsx)(v.Vq,{open:G.showDialog,onOpenChange:en,children:(0,t.jsxs)(v.cZ,{className:"sm:max-w-md",children:[(0,t.jsxs)(v.fK,{className:"",children:[(0,t.jsx)(v.$N,{children:"Delete Workspace"}),(0,t.jsxs)(v.Be,{children:['Are you sure you want to delete workspace "',s,'"? This action cannot be undone.']})]}),G.error&&(0,t.jsx)(L.X,{error:G.error,title:"Deletion Failed",onDismiss:()=>q(e=>({...e,error:null}))}),(0,t.jsxs)(v.cN,{className:"",children:[(0,t.jsx)(h.z,{variant:"outline",onClick:en,disabled:G.deleting,children:"Cancel"}),(0,t.jsx)(h.z,{variant:"destructive",onClick:el,disabled:G.deleting,children:G.deleting?"Deleting...":"Delete"})]})]})})]})]})}}}]);