mainsequence 9.0.21.dev63__py3-none-any.whl → 9.0.22.dev65__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.
mainsequence/cli/api.py CHANGED
@@ -63,7 +63,27 @@ MCP_PROTOCOL_VERSION = "2025-11-25"
63
63
  MCP_CLIENT_NAME = "mainsequence-cli"
64
64
  MCP_MAX_RESOURCE_PAGES = 100
65
65
 
66
- S = requests.Session()
66
+
67
+ class _CliSession(requests.Session):
68
+ """The CLI's HTTP session.
69
+
70
+ A request sent without its own timeout gets the SDK's default request
71
+ budget: 5 s to connect, 120 s to read and 125 s in total, inside any budget
72
+ that is already running.
73
+ """
74
+
75
+ def request(self, method, url, **kwargs):
76
+ if kwargs.get("timeout") is not None:
77
+ return super().request(method, url, **kwargs)
78
+ # The CLI imports the SDK client on its first request, not when it starts.
79
+ from mainsequence._transport import DEFAULT_TIMEOUT, request_budget
80
+
81
+ with request_budget(DEFAULT_TIMEOUT) as budget:
82
+ kwargs["timeout"] = budget.timeout()
83
+ return super().request(method, url, **kwargs)
84
+
85
+
86
+ S = _CliSession()
67
87
  S.headers.update({"Content-Type": "application/json"})
68
88
 
69
89
 
@@ -494,7 +514,7 @@ def _mcp_json_rpc(
494
514
  default request budget: 5 s to connect, 120 s to read, 125 s in total.
495
515
  """
496
516
  # The CLI imports the SDK client only where it needs it.
497
- from mainsequence.client._transport import DEFAULT_TIMEOUT, request_budget
517
+ from mainsequence._transport import DEFAULT_TIMEOUT, request_budget
498
518
 
499
519
  payload = {
500
520
  "jsonrpc": "2.0",
mainsequence/cli/cli.py CHANGED
@@ -1946,6 +1946,10 @@ def login(
1946
1946
  except ApiError as e:
1947
1947
  error(f"Login failed: {e}")
1948
1948
  raise typer.Exit(1) from e
1949
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
1950
+ # Never echo the exception: a transport error can carry request details.
1951
+ error(f"The backend at {normalized_backend} could not be reached ({type(e).__name__}).")
1952
+ raise typer.Exit(1) from e
1949
1953
  finally:
1950
1954
  if previous_backend_override is None:
1951
1955
  os.environ.pop("MAINSEQUENCE_ENDPOINT", None)
@@ -12,7 +12,7 @@ from typing import Any, ClassVar, Literal
12
12
  import requests
13
13
  from pydantic import ConfigDict, Field, model_validator
14
14
 
15
- from ._transport import request_budget, request_within_budget
15
+ from .._transport import request_budget, request_within_budget
16
16
  from .base import (
17
17
  BaseObjectOrm,
18
18
  BasePydanticModel,
@@ -28,10 +28,10 @@ from mainsequence.runtime_credential_exchange import (
28
28
  identity_token_file_from_environment,
29
29
  )
30
30
 
31
- from ._transport import DEFAULT_ALLOWED_METHODS as DEFAULT_ALLOWED_METHODS
32
- from ._transport import DEFAULT_STATUS_FORCELIST as DEFAULT_STATUS_FORCELIST
33
- from ._transport import DEFAULT_TIMEOUT as DEFAULT_TIMEOUT
34
- from ._transport import DeadlineHTTPAdapter, remaining_timeout, request_budget
31
+ from .._transport import DEFAULT_ALLOWED_METHODS as DEFAULT_ALLOWED_METHODS
32
+ from .._transport import DEFAULT_STATUS_FORCELIST as DEFAULT_STATUS_FORCELIST
33
+ from .._transport import DEFAULT_TIMEOUT as DEFAULT_TIMEOUT
34
+ from .._transport import DeadlineHTTPAdapter, remaining_timeout, request_budget
35
35
 
36
36
  # ---- Backend defaults (single source of truth) ----
37
37
  MAINSEQUENCE_ENDPOINT = resolve_backend_endpoint()
mainsequence/logconf.py CHANGED
@@ -5,7 +5,6 @@ import logging
5
5
  import logging.config
6
6
  import os
7
7
  import sys
8
- import time
9
8
  import traceback
10
9
  from collections.abc import Mapping
11
10
  from importlib.metadata import PackageNotFoundError
@@ -20,6 +19,7 @@ from structlog.contextvars import bind_contextvars, unbind_contextvars
20
19
  from structlog.dev import ConsoleRenderer
21
20
  from structlog.stdlib import BoundLogger
22
21
 
22
+ from ._transport import DeadlineHTTPAdapter, request_budget
23
23
  from .defaults import resolve_backend_endpoint
24
24
  from .instrumentation import OTelJSONRenderer
25
25
  from .repository_identity_security import UNSUPPORTED_SOURCE_IDENTITY_ENV_NAMES
@@ -76,14 +76,27 @@ class CustomConsoleRenderer(ConsoleRenderer):
76
76
  return rendered
77
77
 
78
78
 
79
- def _request_job_startup_state(*, timeout_s: float = 10.0) -> dict[str, Any]:
79
+ def _request_job_startup_state(*, timeout_s: float | None = None) -> dict[str, Any]:
80
80
  """
81
81
  Fetch startup state from backend using current env vars (JWT auth, endpoint, job_run_uid).
82
82
  Safe to call later after auth when access/refresh tokens become available.
83
+
84
+ The whole read, token renewal included, ends within one request budget:
85
+ ``timeout_s`` seconds in total, or the SDK's default request budget when it is
86
+ None. The GET is retried like any other SDK read. The startup state only
87
+ enriches the logger, so when the backend cannot be reached or does not answer
88
+ in time this reports it on stderr and returns an empty state instead of
89
+ failing the import.
83
90
  """
84
91
  if not is_running_in_pod():
85
92
  return {}
86
93
 
94
+ with request_budget(timeout_s) as budget:
95
+ return _read_job_startup_state(budget)
96
+
97
+
98
+ def _read_job_startup_state(budget) -> dict[str, Any]:
99
+
87
100
  auth_mode = (os.getenv("MAINSEQUENCE_AUTH_MODE") or "jwt").strip().lower()
88
101
 
89
102
  def _backend_base_url() -> str:
@@ -110,7 +123,7 @@ def _request_job_startup_state(*, timeout_s: float = 10.0) -> dict[str, Any]:
110
123
  token_url,
111
124
  headers={"Content-Type": "application/json"},
112
125
  json=body,
113
- timeout=timeout_s,
126
+ timeout=budget.timeout(),
114
127
  allow_redirects=False,
115
128
  )
116
129
 
@@ -119,7 +132,7 @@ def _request_job_startup_state(*, timeout_s: float = 10.0) -> dict[str, Any]:
119
132
  _post,
120
133
  credential_id=(os.getenv(RUNTIME_CREDENTIAL_ID_ENV) or "").strip(),
121
134
  identity_token_file=identity_token_file_from_environment(),
122
- deadline=time.monotonic() + timeout_s,
135
+ deadline=budget.deadline,
123
136
  )
124
137
  except Exception:
125
138
  return False
@@ -155,7 +168,7 @@ def _request_job_startup_state(*, timeout_s: float = 10.0) -> dict[str, Any]:
155
168
  f"{_backend_base_url()}/auth/jwt-token/token/refresh/",
156
169
  headers={"Content-Type": "application/json"},
157
170
  json={"refresh": refresh_token},
158
- timeout=timeout_s,
171
+ timeout=budget.timeout(),
159
172
  )
160
173
  except Exception:
161
174
  return False
@@ -202,10 +215,25 @@ def _request_job_startup_state(*, timeout_s: float = 10.0) -> dict[str, Any]:
202
215
  endpoint = f"{_backend_base_url()}/api/v1/job-runs/{job_run_uid}/startup-state/"
203
216
  params: dict[str, Any] = {}
204
217
 
205
- resp = requests.get(endpoint, headers=headers, params=params, timeout=timeout_s)
206
- if resp.status_code in {401, 403} and using_jwt and _refresh_access_token():
207
- headers, _ = _auth_headers()
208
- resp = requests.get(endpoint, headers=headers, params=params, timeout=timeout_s)
218
+ try:
219
+ with requests.Session() as session:
220
+ adapter = DeadlineHTTPAdapter(retries=3, backoff_factor=0.5)
221
+ session.mount("https://", adapter)
222
+ session.mount("http://", adapter)
223
+ resp = session.get(endpoint, headers=headers, params=params, timeout=budget.timeout())
224
+ if resp.status_code in {401, 403} and using_jwt and _refresh_access_token():
225
+ headers, _ = _auth_headers()
226
+ resp = session.get(
227
+ endpoint, headers=headers, params=params, timeout=budget.timeout()
228
+ )
229
+ except requests.RequestException as exc:
230
+ # The exception text is not echoed: it can carry the request URL and headers.
231
+ print(
232
+ "mainsequence: the Job startup state could not be read "
233
+ f"({type(exc).__name__}); continuing without it.",
234
+ file=sys.stderr,
235
+ )
236
+ return {}
209
237
 
210
238
  if resp.status_code != 200:
211
239
  # Do not spam stdout during CLI usage when auth context is not present yet.
@@ -426,7 +454,7 @@ def build_application_logger(application_name: str = "ms-sdk", **metadata):
426
454
  return logger
427
455
 
428
456
 
429
- def refresh_application_logger_bindings(*, timeout_s: float = 10.0) -> BoundLogger:
457
+ def refresh_application_logger_bindings(*, timeout_s: float | None = None) -> BoundLogger:
430
458
  """
431
459
  Importable helper: call this AFTER the user authenticates / token exists.
432
460
  It re-fetches backend state and binds the same keys as build_application_logger().
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 9.0.21.dev63
3
+ Version: 9.0.22.dev65
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: MIT
@@ -11,19 +11,20 @@ agent_scaffold/skills/sdk_code_repository_execution/SKILL.md,sha256=zly7MheB-Sjd
11
11
  mainsequence/__init__.py,sha256=70CbIo7ZG1mzi5uwkqDtVESunBE0RanHjeFg4ptfsA0,385
12
12
  mainsequence/__main__.py,sha256=KuAP69uduS2ImrPIgNMzbGBRepZzjqvUONu5vFh4v4A,176
13
13
  mainsequence/_request_identity.py,sha256=vsbnQeJ9ycRB2p8oFTUxfPkzDh7-SBQ5Pxzn04pjqVM,4183
14
+ mainsequence/_transport.py,sha256=gFSw5bl4O2o3NwZOk-ueNnTQvySBecyhC2-7OPb-BPk,8425
14
15
  mainsequence/bootstrap.py,sha256=AvG55Y46RcshVqJZsyqvUDN62CJg4CDbHu0Bvsbwfzs,2910
15
16
  mainsequence/code_repository_context.py,sha256=JRrWjwpnUy8I-OjSd9hHlrcq8-Ow3Zeh_N6cYvmtA3o,28545
16
17
  mainsequence/code_repository_skills.py,sha256=AyC1HFAxQXHXNBGbarVUJS2cHd4X5HwDx6u8XBophbE,23611
17
18
  mainsequence/defaults.py,sha256=5r6s7AI-X40rpm1D3IgcNsWam7iJg4_lIxB3F_1u2z0,408
18
- mainsequence/logconf.py,sha256=IxBuadkLTIBijNiEyid7rCkrZZP01WAQNaVhrj0OCuk,19005
19
+ mainsequence/logconf.py,sha256=q4aTXScwR3lW1nOB3KdamwUDRR7dNeKvmfFtp36msI0,20274
19
20
  mainsequence/repository_identity_security.py,sha256=Cdywndhiy1zJFUPh-FfM90RjZ3Hg3tjhxBr64FRhmXs,1035
20
21
  mainsequence/runtime_credential_exchange.py,sha256=D-gDGHljKnGbMfOiUi_uJ9b5yVFkYB1CB2hikYxTdO4,6152
21
22
  mainsequence/runtime_flags.py,sha256=1eTM7VRRhnuZ_GXcmFhqAmdEKwyQYMyZcPsio88vJRQ,378
22
23
  mainsequence/scaffold_skills.py,sha256=k0V6eKC5AwbHr9wB9UGMJNF-8eJ0_1izF4-BU01KHrg,10606
23
24
  mainsequence/cli/__init__.py,sha256=sMWTnomCaISjnureLuXFRqOzkH2Y77uYEivsd-M6_8Y,28
24
- mainsequence/cli/api.py,sha256=QmdNd_dt0l0YQdWn0qKVTWjrQD7DXgIBl-tsfdzffBc,128140
25
+ mainsequence/cli/api.py,sha256=u3vjwJ5w_u4o76ufkePXKIh6dixOHDok_r8Os0iA_04,128865
25
26
  mainsequence/cli/browser_auth.py,sha256=Vb5MBOnPC05HLZzVNUDODobJ_p5wEIzH_h8rIlpShlE,9984
26
- mainsequence/cli/cli.py,sha256=zV6vMnOkVjZrdNjknCRgqxSJAkYHYt7D874F8uAfgZI,269549
27
+ mainsequence/cli/cli.py,sha256=8EM0j2rRvGX-t1ByfkVD8HUUx5QgK1BiurCImZfjqYI,269846
27
28
  mainsequence/cli/code_repository_status.py,sha256=SHEnU8YSWFowPG6dEdE4Yd8CS1tIcJSE1MXFLfvD3Os,5975
28
29
  mainsequence/cli/config.py,sha256=TJYnM11kg5pH2O7AGWFLpNEkC9UEu0g7k2RLjhAHCzo,45724
29
30
  mainsequence/cli/docker_utils.py,sha256=eAnp9MI042cHWwKasA5IF2I7INL3A6tV2JX-cSIe7rc,10687
@@ -35,8 +36,7 @@ mainsequence/cli/sdk_utils.py,sha256=SFuSzh8WDNf21mLiDa51K8Fqe5XiLWh2KhHj8zUjCXo
35
36
  mainsequence/cli/ssh_utils.py,sha256=m7HgFHZQ545sxY9igfQLpaNjWHm4SiN9VwCkAuGy2yw,11124
36
37
  mainsequence/cli/ui.py,sha256=memd6pmjALfGj8zndGCx_z80ztQeqTcOvcofU0un34U,2277
37
38
  mainsequence/client/__init__.py,sha256=lsr9JWxeVqOG0KRoHErW5M40ACPSR7SAEE508dTN5XM,672
38
- mainsequence/client/_transport.py,sha256=gFSw5bl4O2o3NwZOk-ueNnTQvySBecyhC2-7OPb-BPk,8425
39
- mainsequence/client/agent_runtime_models.py,sha256=6YbX1K8fA4UxbuOb9GMjbHYJJ9z50oKHExRITp78yyA,74616
39
+ mainsequence/client/agent_runtime_models.py,sha256=xjXq0MyLGrXvwJRsz39EDv56I4X-iCbMn51nB_5Yte8,74617
40
40
  mainsequence/client/base.py,sha256=ZxuwUIscBNQGvByd7NGbn03VtNU9nazqkEouyKfVLk0,43129
41
41
  mainsequence/client/client.py,sha256=Jaw9RYObdVDS5J2O0pln99-XpuW9e4vW-SsbJRNldfw,3866
42
42
  mainsequence/client/compute_validation.py,sha256=ByeX0CkIE7qq06ggZFfN92U3Qp2e9cvo2RTIGWa5_Vw,6662
@@ -47,16 +47,16 @@ mainsequence/client/models_foundry.py,sha256=JwQdPLDSO4dZI-KC0SUdmN6izmNIKMb9IzZ
47
47
  mainsequence/client/models_helpers.py,sha256=CQRjBjx3DsREJLRk0vqFsC-Dcll5lUP2ofu6e5UMvhk,49051
48
48
  mainsequence/client/models_user.py,sha256=aOs_ouI1j9sr2oAaV94xhrD6KcN1DYKBcjPMZxkhD88,46235
49
49
  mainsequence/client/observability.py,sha256=_WrDKCaNk_TXmKElKCXC1inork6Exxxe8FRY4m1GI9s,16008
50
- mainsequence/client/utils.py,sha256=C0h0U-fOBZw3UxIwHcBTLf8CDClAOatf_oRgjvqFth4,31637
50
+ mainsequence/client/utils.py,sha256=Rk2z-78swO6GepibLlQNdxL1eqYy8vHGrjiZdV3G5CM,31641
51
51
  mainsequence/client/value_sets.py,sha256=7TA-mpuAd3hOduNgO1WTQQTuNOGaFgue-VqCsDWKaC8,6181
52
52
  mainsequence/instrumentation/__init__.py,sha256=J_9XmfjprElvz0u2t9rTyJT0kFdyKpxdbkdQiaw28_U,316
53
53
  mainsequence/instrumentation/utils.py,sha256=b-ihjSioAepeLfMpudM2SqCtvnxMqIXileHx3klBCQU,3377
54
54
  mainsequence/server/__init__.py,sha256=BHUAhtdWPDRdxX07NYO_6wrBv5mf0GsXflecO52PmUc,73
55
55
  mainsequence/server/caller_assertions.py,sha256=7OEgPXxiGv-P98K6-XVKFSiNcYZkEFDmehBc_oJKkFM,12563
56
56
  mainsequence/server/fastapi.py,sha256=ZMtazH8ZN2C82uTkBtl0UQdx6F9mlCHD1HurgZQdHbU,18512
57
- mainsequence-9.0.21.dev63.dist-info/licenses/LICENSE,sha256=b6Ecsk8wjsEby3lbSzdljR6hHiFGHBizt9iWV2p1c_I,1074
58
- mainsequence-9.0.21.dev63.dist-info/METADATA,sha256=DZE9U8O44iaS_t1bomPj8dw2f4nmR2tGylZyBkW1HLM,7467
59
- mainsequence-9.0.21.dev63.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
60
- mainsequence-9.0.21.dev63.dist-info/entry_points.txt,sha256=XGAOq3-mF2lWWpeRJvoaWyMarRcK167mH1g124aGdUs,88
61
- mainsequence-9.0.21.dev63.dist-info/top_level.txt,sha256=BFeblM_Q9q0cUWBvTxnfWcWQEHtf5XZi9nOS-Gdfwus,28
62
- mainsequence-9.0.21.dev63.dist-info/RECORD,,
57
+ mainsequence-9.0.22.dev65.dist-info/licenses/LICENSE,sha256=b6Ecsk8wjsEby3lbSzdljR6hHiFGHBizt9iWV2p1c_I,1074
58
+ mainsequence-9.0.22.dev65.dist-info/METADATA,sha256=iVI-Dap3Gl5LvTSpmppG5uR9Q8vMKnRauM7J-GURhew,7467
59
+ mainsequence-9.0.22.dev65.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
60
+ mainsequence-9.0.22.dev65.dist-info/entry_points.txt,sha256=XGAOq3-mF2lWWpeRJvoaWyMarRcK167mH1g124aGdUs,88
61
+ mainsequence-9.0.22.dev65.dist-info/top_level.txt,sha256=BFeblM_Q9q0cUWBvTxnfWcWQEHtf5XZi9nOS-Gdfwus,28
62
+ mainsequence-9.0.22.dev65.dist-info/RECORD,,
File without changes