ellipsis-dev 0.45.0__tar.gz → 0.47.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ellipsis-dev
3
- Version: 0.45.0
3
+ Version: 0.47.0
4
4
  Summary: Python SDK for the Ellipsis agents platform: every /v1 operation, sync and async.
5
5
  Project-URL: Homepage, https://www.ellipsis.dev
6
6
  Project-URL: Documentation, https://www.ellipsis.dev/docs/api
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ellipsis-dev"
7
- version = "0.45.0"
7
+ version = "0.47.0"
8
8
  description = "Python SDK for the Ellipsis agents platform: every /v1 operation, sync and async."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -18,13 +18,13 @@ from . import models
18
18
  from ._pagination import AsyncPage, SyncPage
19
19
  from ._session_handle import AsyncSessionsSugar, SyncSessionsSugar
20
20
  from ._transport import (
21
- DEFAULT_BASE_URL,
22
21
  DEFAULT_TIMEOUT_SECONDS,
23
22
  MAX_RETRIES,
24
23
  AsyncTransport,
25
24
  Transport,
26
25
  build_body,
27
26
  build_query,
27
+ new_idempotency_key,
28
28
  )
29
29
 
30
30
 
@@ -747,10 +747,13 @@ class _SyncAgents:
747
747
 
748
748
  def list(
749
749
  self,
750
+ *,
751
+ label: Union[List[str], None] = None,
752
+ annotation: Union[List[str], None] = None,
750
753
  ) -> models.AgentsListResponse:
751
754
  """List Agents
752
755
 
753
- List agents.
756
+ List agents, optionally only those with the given labels and annotations.
754
757
 
755
758
  Required permissions: read:configs.
756
759
  API key: Supported.
@@ -760,8 +763,14 @@ class _SyncAgents:
760
763
  Grant match patterns can further restrict access to individual resources.
761
764
  """
762
765
  path = "/v1/agents"
766
+ query = build_query(
767
+ {
768
+ "label": label,
769
+ "annotation": annotation,
770
+ }
771
+ )
763
772
  response = self._transport.request(
764
- "GET", path, response_model=models.AgentsListResponse
773
+ "GET", path, response_model=models.AgentsListResponse, query=query
765
774
  )
766
775
  assert response is not None
767
776
  return response
@@ -815,9 +824,10 @@ class _SyncAgents:
815
824
  environment: Union[str, models.EnvironmentConfig_Input, None] = None,
816
825
  force_rebuild: Union[bool, None] = None,
817
826
  gemini: Union[models.GeminiOverrides, None] = None,
827
+ idempotency_key: Union[str, None] = None,
818
828
  images: Union[List[models.ImageAttachment], None] = None,
819
829
  input: Union[dict[str, Any], None] = None,
820
- metadata: Union[dict[str, Union[str, None]], None] = None,
830
+ metadata: Union[models.ConfigMetadataPatch, None] = None,
821
831
  output: Union[models.OutputConfig, None] = None,
822
832
  permissions: Union[models.PermissionsConfig_Input, None] = None,
823
833
  skills: Union[List[models.SkillRef_Input], None] = None,
@@ -854,6 +864,9 @@ class _SyncAgents:
854
864
  "environment": environment,
855
865
  "force_rebuild": force_rebuild,
856
866
  "gemini": gemini,
867
+ "idempotency_key": idempotency_key
868
+ if idempotency_key is not None
869
+ else new_idempotency_key(),
857
870
  "images": images,
858
871
  "input": input,
859
872
  "metadata": metadata,
@@ -1191,10 +1204,13 @@ class _SyncEnvironments:
1191
1204
 
1192
1205
  def list(
1193
1206
  self,
1207
+ *,
1208
+ label: Union[List[str], None] = None,
1209
+ annotation: Union[List[str], None] = None,
1194
1210
  ) -> models.EnvironmentsListResponse:
1195
1211
  """List Environments
1196
1212
 
1197
- List saved environments.
1213
+ List saved environments, optionally only those with the given labels and annotations.
1198
1214
 
1199
1215
  Required permissions: read:environments.
1200
1216
  API key: Supported.
@@ -1203,8 +1219,14 @@ class _SyncEnvironments:
1203
1219
  Higher permission levels include lower levels: read < write < delete.
1204
1220
  """
1205
1221
  path = "/v1/environments"
1222
+ query = build_query(
1223
+ {
1224
+ "label": label,
1225
+ "annotation": annotation,
1226
+ }
1227
+ )
1206
1228
  response = self._transport.request(
1207
- "GET", path, response_model=models.EnvironmentsListResponse
1229
+ "GET", path, response_model=models.EnvironmentsListResponse, query=query
1208
1230
  )
1209
1231
  assert response is not None
1210
1232
  return response
@@ -1909,13 +1931,15 @@ class _SyncSessions(SyncSessionsSugar):
1909
1931
  author_id: Union[int, None] = None,
1910
1932
  repo: Union[str, None] = None,
1911
1933
  status: Union[List[models.SessionStatus], None] = None,
1934
+ label: Union[List[str], None] = None,
1935
+ annotation: Union[List[str], None] = None,
1912
1936
  ) -> SyncPage[models.Session, models.SessionsListResponse]:
1913
1937
  """List Sessions
1914
1938
 
1915
1939
  List cloud agent sessions, newest first.
1916
1940
 
1917
1941
  Optionally filtered by agent (id or name), source, service, handler, time
1918
- window, attributed author, repository, and status.
1942
+ window, attributed author, repository, status, labels, and annotations.
1919
1943
 
1920
1944
  Required permissions: read:sessions.
1921
1945
  API key: Supported.
@@ -1939,6 +1963,8 @@ class _SyncSessions(SyncSessionsSugar):
1939
1963
  "author_id": author_id,
1940
1964
  "repo": repo,
1941
1965
  "status": status,
1966
+ "label": label,
1967
+ "annotation": annotation,
1942
1968
  }
1943
1969
  )
1944
1970
  response = self._transport.request(
@@ -1961,6 +1987,8 @@ class _SyncSessions(SyncSessionsSugar):
1961
1987
  author_id=author_id,
1962
1988
  repo=repo,
1963
1989
  status=status,
1990
+ label=label,
1991
+ annotation=annotation,
1964
1992
  ),
1965
1993
  )
1966
1994
 
@@ -2047,20 +2075,23 @@ class _SyncSessions(SyncSessionsSugar):
2047
2075
  Send a message to a session.
2048
2076
 
2049
2077
  409 for mention, non-interactive, closed, archived, and
2050
- out-of-budget sessions. Returns the created message; a retried send with the
2078
+ out-of-budget sessions. 403 when a session token sends to its own
2079
+ session. Returns the created message; a retried send with the
2051
2080
  same idempotency_key returns the original one. The message is
2052
2081
  answered once the session's status is `idle` or `closed`.
2053
2082
 
2054
2083
  Required permissions: write:sessions.
2055
2084
  API key: Supported.
2056
2085
  CLI user token: Supported.
2057
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration.
2086
+ Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Never the token's own session.
2058
2087
  Higher permission levels include lower levels: read < write < delete.
2059
2088
  Grant match patterns can further restrict access to individual resources.
2060
2089
  """
2061
2090
  path = f"/v1/sessions/{_quote(id)}/messages"
2062
2091
  body_fields: dict[str, Any] = {
2063
- "idempotency_key": idempotency_key,
2092
+ "idempotency_key": idempotency_key
2093
+ if idempotency_key is not None
2094
+ else new_idempotency_key(),
2064
2095
  "images": images,
2065
2096
  "message": message,
2066
2097
  }
@@ -2105,8 +2136,9 @@ class _SyncSessions(SyncSessionsSugar):
2105
2136
  environment: Union[str, models.EnvironmentConfig_Input, None] = None,
2106
2137
  force_rebuild: Union[bool, None] = None,
2107
2138
  gemini: Union[models.GeminiConfig_Input, None] = None,
2139
+ idempotency_key: Union[str, None] = None,
2108
2140
  images: Union[List[models.ImageAttachment], None] = None,
2109
- metadata: Union[dict[str, str], None] = None,
2141
+ metadata: Union[models.ConfigMetadata_Input, None] = None,
2110
2142
  output: Union[models.OutputConfig, None] = None,
2111
2143
  permissions: Union[models.PermissionsConfig_Input, None] = None,
2112
2144
  skills: Union[List[models.SkillRef_Input], None] = None,
@@ -2144,6 +2176,9 @@ class _SyncSessions(SyncSessionsSugar):
2144
2176
  "environment": environment,
2145
2177
  "force_rebuild": force_rebuild,
2146
2178
  "gemini": gemini,
2179
+ "idempotency_key": idempotency_key
2180
+ if idempotency_key is not None
2181
+ else new_idempotency_key(),
2147
2182
  "images": images,
2148
2183
  "metadata": metadata,
2149
2184
  "output": output,
@@ -2254,9 +2289,9 @@ class _SyncSessions(SyncSessionsSugar):
2254
2289
  thread, pull request, issue, or Linear issue it answers starts a
2255
2290
  new session. A session is also archived when it closes and after
2256
2291
  the account's `archive_inactive_sessions_days` without a message.
2257
- When the account sets session log retention, the session's
2258
- records, messages, and prompt are deleted that many days after it
2259
- was archived (`archived.deletes_at`). The session itself stays.
2292
+ The session's records, messages, and prompt are deleted the
2293
+ account's `session_logs_days` after it was archived
2294
+ (`archived.deletes_at`). The session itself stays.
2260
2295
 
2261
2296
  Set archived to false to clear both fields and let the session
2262
2297
  take messages again. 409 `session_superseded` when a newer session
@@ -2645,7 +2680,7 @@ class _SyncWebhooksDeliveries:
2645
2680
  List webhook deliveries, newest first.
2646
2681
 
2647
2682
  Includes verification pings and deliveries for deleted webhooks.
2648
- History is retained for 30 days. Retries belong to the same delivery.
2683
+ History is kept for 7 days. Retries belong to the same delivery.
2649
2684
 
2650
2685
  Required permissions: read:webhooks.
2651
2686
  API key: Supported.
@@ -2740,7 +2775,7 @@ class _SyncWebhooks:
2740
2775
  Delete a webhook and cancel outstanding deliveries.
2741
2776
 
2742
2777
  An HTTP request already in flight may finish. Delivery history remains
2743
- available for 30 days. Deletion frees a webhook slot.
2778
+ available for 7 days. Deletion frees a webhook slot.
2744
2779
 
2745
2780
  Required permissions: delete:webhooks.
2746
2781
  API key: Supported.
@@ -2864,7 +2899,7 @@ class Ellipsis:
2864
2899
  self,
2865
2900
  api_key: str,
2866
2901
  *,
2867
- base_url: str = DEFAULT_BASE_URL,
2902
+ base_url: str | None = None,
2868
2903
  timeout: float = DEFAULT_TIMEOUT_SECONDS,
2869
2904
  max_retries: int = MAX_RETRIES,
2870
2905
  ) -> None:
@@ -3639,10 +3674,13 @@ class _AsyncAgents:
3639
3674
 
3640
3675
  async def list(
3641
3676
  self,
3677
+ *,
3678
+ label: Union[List[str], None] = None,
3679
+ annotation: Union[List[str], None] = None,
3642
3680
  ) -> models.AgentsListResponse:
3643
3681
  """List Agents
3644
3682
 
3645
- List agents.
3683
+ List agents, optionally only those with the given labels and annotations.
3646
3684
 
3647
3685
  Required permissions: read:configs.
3648
3686
  API key: Supported.
@@ -3652,8 +3690,14 @@ class _AsyncAgents:
3652
3690
  Grant match patterns can further restrict access to individual resources.
3653
3691
  """
3654
3692
  path = "/v1/agents"
3693
+ query = build_query(
3694
+ {
3695
+ "label": label,
3696
+ "annotation": annotation,
3697
+ }
3698
+ )
3655
3699
  response = await self._transport.request(
3656
- "GET", path, response_model=models.AgentsListResponse
3700
+ "GET", path, response_model=models.AgentsListResponse, query=query
3657
3701
  )
3658
3702
  assert response is not None
3659
3703
  return response
@@ -3707,9 +3751,10 @@ class _AsyncAgents:
3707
3751
  environment: Union[str, models.EnvironmentConfig_Input, None] = None,
3708
3752
  force_rebuild: Union[bool, None] = None,
3709
3753
  gemini: Union[models.GeminiOverrides, None] = None,
3754
+ idempotency_key: Union[str, None] = None,
3710
3755
  images: Union[List[models.ImageAttachment], None] = None,
3711
3756
  input: Union[dict[str, Any], None] = None,
3712
- metadata: Union[dict[str, Union[str, None]], None] = None,
3757
+ metadata: Union[models.ConfigMetadataPatch, None] = None,
3713
3758
  output: Union[models.OutputConfig, None] = None,
3714
3759
  permissions: Union[models.PermissionsConfig_Input, None] = None,
3715
3760
  skills: Union[List[models.SkillRef_Input], None] = None,
@@ -3746,6 +3791,9 @@ class _AsyncAgents:
3746
3791
  "environment": environment,
3747
3792
  "force_rebuild": force_rebuild,
3748
3793
  "gemini": gemini,
3794
+ "idempotency_key": idempotency_key
3795
+ if idempotency_key is not None
3796
+ else new_idempotency_key(),
3749
3797
  "images": images,
3750
3798
  "input": input,
3751
3799
  "metadata": metadata,
@@ -4083,10 +4131,13 @@ class _AsyncEnvironments:
4083
4131
 
4084
4132
  async def list(
4085
4133
  self,
4134
+ *,
4135
+ label: Union[List[str], None] = None,
4136
+ annotation: Union[List[str], None] = None,
4086
4137
  ) -> models.EnvironmentsListResponse:
4087
4138
  """List Environments
4088
4139
 
4089
- List saved environments.
4140
+ List saved environments, optionally only those with the given labels and annotations.
4090
4141
 
4091
4142
  Required permissions: read:environments.
4092
4143
  API key: Supported.
@@ -4095,8 +4146,14 @@ class _AsyncEnvironments:
4095
4146
  Higher permission levels include lower levels: read < write < delete.
4096
4147
  """
4097
4148
  path = "/v1/environments"
4149
+ query = build_query(
4150
+ {
4151
+ "label": label,
4152
+ "annotation": annotation,
4153
+ }
4154
+ )
4098
4155
  response = await self._transport.request(
4099
- "GET", path, response_model=models.EnvironmentsListResponse
4156
+ "GET", path, response_model=models.EnvironmentsListResponse, query=query
4100
4157
  )
4101
4158
  assert response is not None
4102
4159
  return response
@@ -4803,13 +4860,15 @@ class _AsyncSessions(AsyncSessionsSugar):
4803
4860
  author_id: Union[int, None] = None,
4804
4861
  repo: Union[str, None] = None,
4805
4862
  status: Union[List[models.SessionStatus], None] = None,
4863
+ label: Union[List[str], None] = None,
4864
+ annotation: Union[List[str], None] = None,
4806
4865
  ) -> AsyncPage[models.Session, models.SessionsListResponse]:
4807
4866
  """List Sessions
4808
4867
 
4809
4868
  List cloud agent sessions, newest first.
4810
4869
 
4811
4870
  Optionally filtered by agent (id or name), source, service, handler, time
4812
- window, attributed author, repository, and status.
4871
+ window, attributed author, repository, status, labels, and annotations.
4813
4872
 
4814
4873
  Required permissions: read:sessions.
4815
4874
  API key: Supported.
@@ -4833,6 +4892,8 @@ class _AsyncSessions(AsyncSessionsSugar):
4833
4892
  "author_id": author_id,
4834
4893
  "repo": repo,
4835
4894
  "status": status,
4895
+ "label": label,
4896
+ "annotation": annotation,
4836
4897
  }
4837
4898
  )
4838
4899
  response = await self._transport.request(
@@ -4855,6 +4916,8 @@ class _AsyncSessions(AsyncSessionsSugar):
4855
4916
  author_id=author_id,
4856
4917
  repo=repo,
4857
4918
  status=status,
4919
+ label=label,
4920
+ annotation=annotation,
4858
4921
  ),
4859
4922
  )
4860
4923
 
@@ -4941,20 +5004,23 @@ class _AsyncSessions(AsyncSessionsSugar):
4941
5004
  Send a message to a session.
4942
5005
 
4943
5006
  409 for mention, non-interactive, closed, archived, and
4944
- out-of-budget sessions. Returns the created message; a retried send with the
5007
+ out-of-budget sessions. 403 when a session token sends to its own
5008
+ session. Returns the created message; a retried send with the
4945
5009
  same idempotency_key returns the original one. The message is
4946
5010
  answered once the session's status is `idle` or `closed`.
4947
5011
 
4948
5012
  Required permissions: write:sessions.
4949
5013
  API key: Supported.
4950
5014
  CLI user token: Supported.
4951
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration.
5015
+ Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Never the token's own session.
4952
5016
  Higher permission levels include lower levels: read < write < delete.
4953
5017
  Grant match patterns can further restrict access to individual resources.
4954
5018
  """
4955
5019
  path = f"/v1/sessions/{_quote(id)}/messages"
4956
5020
  body_fields: dict[str, Any] = {
4957
- "idempotency_key": idempotency_key,
5021
+ "idempotency_key": idempotency_key
5022
+ if idempotency_key is not None
5023
+ else new_idempotency_key(),
4958
5024
  "images": images,
4959
5025
  "message": message,
4960
5026
  }
@@ -4999,8 +5065,9 @@ class _AsyncSessions(AsyncSessionsSugar):
4999
5065
  environment: Union[str, models.EnvironmentConfig_Input, None] = None,
5000
5066
  force_rebuild: Union[bool, None] = None,
5001
5067
  gemini: Union[models.GeminiConfig_Input, None] = None,
5068
+ idempotency_key: Union[str, None] = None,
5002
5069
  images: Union[List[models.ImageAttachment], None] = None,
5003
- metadata: Union[dict[str, str], None] = None,
5070
+ metadata: Union[models.ConfigMetadata_Input, None] = None,
5004
5071
  output: Union[models.OutputConfig, None] = None,
5005
5072
  permissions: Union[models.PermissionsConfig_Input, None] = None,
5006
5073
  skills: Union[List[models.SkillRef_Input], None] = None,
@@ -5038,6 +5105,9 @@ class _AsyncSessions(AsyncSessionsSugar):
5038
5105
  "environment": environment,
5039
5106
  "force_rebuild": force_rebuild,
5040
5107
  "gemini": gemini,
5108
+ "idempotency_key": idempotency_key
5109
+ if idempotency_key is not None
5110
+ else new_idempotency_key(),
5041
5111
  "images": images,
5042
5112
  "metadata": metadata,
5043
5113
  "output": output,
@@ -5148,9 +5218,9 @@ class _AsyncSessions(AsyncSessionsSugar):
5148
5218
  thread, pull request, issue, or Linear issue it answers starts a
5149
5219
  new session. A session is also archived when it closes and after
5150
5220
  the account's `archive_inactive_sessions_days` without a message.
5151
- When the account sets session log retention, the session's
5152
- records, messages, and prompt are deleted that many days after it
5153
- was archived (`archived.deletes_at`). The session itself stays.
5221
+ The session's records, messages, and prompt are deleted the
5222
+ account's `session_logs_days` after it was archived
5223
+ (`archived.deletes_at`). The session itself stays.
5154
5224
 
5155
5225
  Set archived to false to clear both fields and let the session
5156
5226
  take messages again. 409 `session_superseded` when a newer session
@@ -5539,7 +5609,7 @@ class _AsyncWebhooksDeliveries:
5539
5609
  List webhook deliveries, newest first.
5540
5610
 
5541
5611
  Includes verification pings and deliveries for deleted webhooks.
5542
- History is retained for 30 days. Retries belong to the same delivery.
5612
+ History is kept for 7 days. Retries belong to the same delivery.
5543
5613
 
5544
5614
  Required permissions: read:webhooks.
5545
5615
  API key: Supported.
@@ -5634,7 +5704,7 @@ class _AsyncWebhooks:
5634
5704
  Delete a webhook and cancel outstanding deliveries.
5635
5705
 
5636
5706
  An HTTP request already in flight may finish. Delivery history remains
5637
- available for 30 days. Deletion frees a webhook slot.
5707
+ available for 7 days. Deletion frees a webhook slot.
5638
5708
 
5639
5709
  Required permissions: delete:webhooks.
5640
5710
  API key: Supported.
@@ -5758,7 +5828,7 @@ class AsyncEllipsis:
5758
5828
  self,
5759
5829
  api_key: str,
5760
5830
  *,
5761
- base_url: str = DEFAULT_BASE_URL,
5831
+ base_url: str | None = None,
5762
5832
  timeout: float = DEFAULT_TIMEOUT_SECONDS,
5763
5833
  max_retries: int = MAX_RETRIES,
5764
5834
  ) -> None:
@@ -17,7 +17,7 @@ from urllib.parse import quote
17
17
 
18
18
  from . import frames
19
19
  from ._errors import EllipsisError
20
- from ._transport import DEFAULT_BASE_URL
20
+ from ._transport import resolve_base_url
21
21
 
22
22
  WS_CLOSE_NORMAL = 1000
23
23
  WS_CLOSE_AUTH_FAILED = 1008
@@ -146,7 +146,7 @@ async def stream_session(
146
146
  session_id: str,
147
147
  api_key: str,
148
148
  on_frame: Callable[[frames.StreamFrame], None | Awaitable[None]],
149
- base_url: str = DEFAULT_BASE_URL,
149
+ base_url: str | None = None,
150
150
  after_seq: int = 0,
151
151
  max_reconnects: int = DEFAULT_MAX_RECONNECTS,
152
152
  ) -> StreamOutcome:
@@ -164,6 +164,7 @@ async def stream_session(
164
164
  "the stream client needs the `websockets` package: pip install 'ellipsis-dev[stream]'"
165
165
  ) from exc
166
166
 
167
+ resolved_base_url = resolve_base_url(base_url)
167
168
  cursor = after_seq
168
169
  ever_received_frame = False
169
170
  attempt = 0
@@ -190,7 +191,7 @@ async def stream_session(
190
191
  failure = ""
191
192
  try:
192
193
  async with websockets.connect(
193
- _ws_url(base_url, session_id, cursor),
194
+ _ws_url(resolved_base_url, session_id, cursor),
194
195
  additional_headers={"Authorization": f"Bearer {api_key}"},
195
196
  ) as socket:
196
197
  while True:
@@ -5,8 +5,10 @@ calls `request()` with an operation's method/path/params/body and a response
5
5
  model; everything HTTP lives here.
6
6
  """
7
7
 
8
+ import os
8
9
  import random
9
10
  import time
11
+ import uuid
10
12
  from typing import Any, TypeVar
11
13
 
12
14
  import httpx
@@ -16,6 +18,9 @@ from pydantic_core import to_jsonable_python
16
18
  from ._errors import APIError, TransportError, api_error_for
17
19
 
18
20
  DEFAULT_BASE_URL = "https://api.ellipsis.dev"
21
+ # Read when the caller passes no base_url. Session VMs set it, so code running
22
+ # inside a session reaches the API without configuration.
23
+ BASE_URL_ENV = "ELLIPSIS_API_BASE_URL"
19
24
  DEFAULT_TIMEOUT_SECONDS = 60.0
20
25
  # 429 and 5xx are retried with exponential backoff + jitter; everything else
21
26
  # raises immediately. Only idempotent-safe by the API's own semantics: every
@@ -56,6 +61,18 @@ def build_body(body: BaseModel) -> Any:
56
61
  return body.model_dump(mode="json", by_alias=True, exclude_none=True)
57
62
 
58
63
 
64
+ def new_idempotency_key() -> str:
65
+ """A fresh key for a request that takes `idempotency_key`. The generated
66
+ client makes one per call when the caller sets none, so the transport's
67
+ retries of that call reuse it and the server applies the call once."""
68
+ return uuid.uuid4().hex
69
+
70
+
71
+ def resolve_base_url(base_url: str | None) -> str:
72
+ """The explicit base URL, else ELLIPSIS_API_BASE_URL, else the default."""
73
+ return (base_url or os.environ.get(BASE_URL_ENV) or DEFAULT_BASE_URL).rstrip("/")
74
+
75
+
59
76
  def _backoff_seconds(attempt: int, retry_after: str | None) -> float:
60
77
  if retry_after is not None:
61
78
  try:
@@ -72,13 +89,13 @@ class Transport:
72
89
  self,
73
90
  *,
74
91
  api_key: str,
75
- base_url: str = DEFAULT_BASE_URL,
92
+ base_url: str | None = None,
76
93
  timeout: float = DEFAULT_TIMEOUT_SECONDS,
77
94
  max_retries: int = MAX_RETRIES,
78
95
  ) -> None:
79
96
  self._max_retries = max_retries
80
97
  # Public so the stream client / sugar can reuse the credential.
81
- self.base_url = base_url.rstrip("/")
98
+ self.base_url = resolve_base_url(base_url)
82
99
  self.api_key = api_key
83
100
  self._client = httpx.Client(
84
101
  base_url=self.base_url,
@@ -124,12 +141,12 @@ class AsyncTransport:
124
141
  self,
125
142
  *,
126
143
  api_key: str,
127
- base_url: str = DEFAULT_BASE_URL,
144
+ base_url: str | None = None,
128
145
  timeout: float = DEFAULT_TIMEOUT_SECONDS,
129
146
  max_retries: int = MAX_RETRIES,
130
147
  ) -> None:
131
148
  self._max_retries = max_retries
132
- self.base_url = base_url.rstrip("/")
149
+ self.base_url = resolve_base_url(base_url)
133
150
  self.api_key = api_key
134
151
  self._client = httpx.AsyncClient(
135
152
  base_url=self.base_url,
@@ -116,20 +116,14 @@ class BudgetCeiling(EllipsisModel):
116
116
 
117
117
 
118
118
  class DataRetentionSettings_Output(EllipsisModel):
119
- api_requests_days: Union[int, None] = Field(
120
- description="Reserved; API request retention is not yet enforced."
121
- )
122
119
  archive_inactive_sessions_days: int = Field(
123
120
  description="Days without a message before a session is archived. A session is also archived when it closes. 1 to 90, default 7."
124
121
  )
125
122
  environment_snapshots_days: int = Field(
126
- description="Retention of newly created reusable environment snapshots, up to 28 days from creation. Paused session workspaces have a separate lifetime."
127
- )
128
- session_logs_days: Union[int, None] = Field(
129
- description="Days after a session is archived before its records and messages are deleted. The session itself stays. Null keeps them."
123
+ description="Retention of newly created reusable environment snapshots, up to 7 days from creation. Paused session workspaces have a separate lifetime."
130
124
  )
131
- webhooks_days: Union[int, None] = Field(
132
- description="Reserved; webhook retention is not yet enforced."
125
+ session_logs_days: int = Field(
126
+ description="Days after a session is archived before its records and messages are deleted. The session itself stays. 1 to 365, default 365."
133
127
  )
134
128
 
135
129
 
@@ -171,8 +165,12 @@ class AccountSettings(EllipsisModel):
171
165
 
172
166
 
173
167
  class ConfigMetadata_Output(EllipsisModel):
174
- annotations: dict[str, str]
175
- labels: list[str]
168
+ """Labels and annotations for your own use, the same block on an agent, an environment, and a session. They never change how anything runs."""
169
+
170
+ annotations: dict[str, str] = Field(
171
+ description="String key/value pairs to tag and filter by. A key contains no `:`."
172
+ )
173
+ labels: list[str] = Field(description="Names to tag and filter by.")
176
174
 
177
175
 
178
176
  class AgentIdentity_Output(EllipsisModel):
@@ -932,8 +930,8 @@ class SessionConfig_Output(EllipsisModel):
932
930
  gemini: Union[GeminiConfig_Output, None] = Field(
933
931
  description="Gemini CLI input and native options. Set exactly one of claude_code, codex, or gemini."
934
932
  )
935
- metadata: dict[str, str] = Field(
936
- description="Arbitrary string key/value metadata stored on every session."
933
+ metadata: ConfigMetadata_Output = Field(
934
+ description="Labels and annotations stored on every session."
937
935
  )
938
936
  output: Union[OutputConfig, None]
939
937
  permissions: PermissionsConfig_Output
@@ -1050,8 +1048,15 @@ class Agent(EllipsisModel):
1050
1048
 
1051
1049
 
1052
1050
  class ConfigMetadata_Input(EllipsisModel):
1053
- annotations: Union[dict[str, str], None] = None
1054
- labels: Union[list[str], None] = None
1051
+ """Labels and annotations for your own use, the same block on an agent, an environment, and a session. They never change how anything runs."""
1052
+
1053
+ annotations: Union[dict[str, str], None] = Field(
1054
+ default=None,
1055
+ description="String key/value pairs to tag and filter by. A key contains no `:`.",
1056
+ )
1057
+ labels: Union[list[str], None] = Field(
1058
+ default=None, description="Names to tag and filter by."
1059
+ )
1055
1060
 
1056
1061
 
1057
1062
  class AgentIdentity_Input(EllipsisModel):
@@ -1692,9 +1697,8 @@ class SessionConfig_Input(EllipsisModel):
1692
1697
  default=None,
1693
1698
  description="Gemini CLI input and native options. Set exactly one of claude_code, codex, or gemini.",
1694
1699
  )
1695
- metadata: Union[dict[str, str], None] = Field(
1696
- default=None,
1697
- description="Arbitrary string key/value metadata stored on every session.",
1700
+ metadata: Union[ConfigMetadata_Input, None] = Field(
1701
+ default=None, description="Labels and annotations stored on every session."
1698
1702
  )
1699
1703
  output: Union[OutputConfig, None] = None
1700
1704
  permissions: Union[PermissionsConfig_Input, None] = None
@@ -3106,6 +3110,18 @@ class CodexSessionRecord(EllipsisRecord):
3106
3110
  tools: Union[list[str], None]
3107
3111
 
3108
3112
 
3113
+ class ConfigMetadataPatch(EllipsisModel):
3114
+ """Metadata merged into an agent's for one session."""
3115
+
3116
+ annotations: Union[dict[str, Union[str, None]], None] = Field(
3117
+ default=None,
3118
+ description="Each key you send sets that key, a `null` value removes the agent's key, and keys you leave out keep the agent's values.",
3119
+ )
3120
+ labels: Union[list[str], None] = Field(
3121
+ default=None, description="Labels added to the agent's."
3122
+ )
3123
+
3124
+
3109
3125
  class CreateAgentRequest(EllipsisModel):
3110
3126
  """Parameters for creating an agent."""
3111
3127
 
@@ -3262,23 +3278,17 @@ class CreditPurchaseReceipt(EllipsisModel):
3262
3278
 
3263
3279
 
3264
3280
  class DataRetentionSettings_Input(EllipsisModel):
3265
- api_requests_days: Union[int, None] = Field(
3266
- default=None, description="Reserved; API request retention is not yet enforced."
3267
- )
3268
3281
  archive_inactive_sessions_days: Union[int, None] = Field(
3269
3282
  default=None,
3270
3283
  description="Days without a message before a session is archived. A session is also archived when it closes. 1 to 90, default 7.",
3271
3284
  )
3272
3285
  environment_snapshots_days: Union[int, None] = Field(
3273
3286
  default=None,
3274
- description="Retention of newly created reusable environment snapshots, up to 28 days from creation. Paused session workspaces have a separate lifetime.",
3287
+ description="Retention of newly created reusable environment snapshots, up to 7 days from creation. Paused session workspaces have a separate lifetime.",
3275
3288
  )
3276
3289
  session_logs_days: Union[int, None] = Field(
3277
3290
  default=None,
3278
- description="Days after a session is archived before its records and messages are deleted. The session itself stays. Null keeps them.",
3279
- )
3280
- webhooks_days: Union[int, None] = Field(
3281
- default=None, description="Reserved; webhook retention is not yet enforced."
3291
+ description="Days after a session is archived before its records and messages are deleted. The session itself stays. 1 to 365, default 365.",
3282
3292
  )
3283
3293
 
3284
3294
 
@@ -3976,7 +3986,7 @@ class IntegrationSessionConfig(EllipsisModel):
3976
3986
  default=None,
3977
3987
  description="Gemini CLI input and native options. Set exactly one of claude_code, codex, or gemini.",
3978
3988
  )
3979
- metadata: Union[dict[str, str], None] = None
3989
+ metadata: Union[ConfigMetadata_Output, None] = None
3980
3990
  permissions: Union[PermissionsConfig_Output, None] = None
3981
3991
  skills: Union[list[SkillRef_Output], None] = None
3982
3992
 
@@ -5373,7 +5383,7 @@ class SendSessionMessageRequest(EllipsisModel):
5373
5383
 
5374
5384
  idempotency_key: Union[str, None] = Field(
5375
5385
  default=None,
5376
- description="Optional retry-safety key, unique per (session, key): a retry with the same key returns the original message — no duplicate message, no double turn, no double spend. The SDK auto-generates one per send.",
5386
+ description="Optional retry safety key, unique per session. A retry with the same key returns the original message, with no second message, turn, or spend. The SDKs make a key for each call that sets none, so their own retries never send a message twice.",
5377
5387
  )
5378
5388
  images: Union[list[ImageAttachment], None] = Field(
5379
5389
  default=None,
@@ -5425,7 +5435,7 @@ class SessionArchived(EllipsisModel):
5425
5435
  description="The GitHub user who archived the session; null if unknown or unavailable."
5426
5436
  )
5427
5437
  deletes_at: Union[datetime, None] = Field(
5428
- description="When the session's records and messages are deleted, from the account's current session log retention. It moves when the retention changes. Null when the account keeps them or they're already deleted."
5438
+ description="When the session's records and messages are deleted, from the account's current session log retention. It moves when the retention changes. Null when they're already deleted."
5429
5439
  )
5430
5440
 
5431
5441
 
@@ -5727,8 +5737,8 @@ class Session(EllipsisModel):
5727
5737
  description="The saved handler and its effective display name when this session started, frozen at creation. Null for built-in responders, legacy sessions, and sessions started without a handler."
5728
5738
  )
5729
5739
  id: str = Field(description="Unique identifier of the session.")
5730
- metadata: dict[str, str] = Field(
5731
- description="Caller-supplied metadata key-value pairs."
5740
+ metadata: ConfigMetadata_Output = Field(
5741
+ description="Labels and annotations set when the session started."
5732
5742
  )
5733
5743
  output: Union[SessionOutput, None] = Field(
5734
5744
  description="The session's structured output: the `json_schema` the agent must satisfy and, once the agent has submitted it, the `result`."
@@ -5938,8 +5948,9 @@ class StartAgentSessionRequest(EllipsisModel):
5938
5948
  """Start a session from a saved agent. The body takes the fields of POST
5939
5949
  /v1/sessions, all optional, plus `input`. The session starts from the
5940
5950
  agent's `session` settings: each field you send replaces the agent's value for that field,
5941
- and each field you leave out keeps it. `metadata` is the exception: it is
5942
- merged into the agent's key by key, and a `null` value removes a key.
5951
+ and each field you leave out keeps it. `metadata` is the exception: its
5952
+ labels are added to the agent's, its annotations merge key by key, and a
5953
+ `null` annotation value removes a key.
5943
5954
  Other lists and maps, and `environment`, are replaced whole, and naming the
5944
5955
  other harness replaces the agent's harness. `input` is required when the
5945
5956
  agent declares `input.json_schema` and rejected when it doesn't. Extra keys
@@ -5974,6 +5985,10 @@ class StartAgentSessionRequest(EllipsisModel):
5974
5985
  default=None,
5975
5986
  description="Gemini CLI options for this session. Sent fields replace the agent's; naming Gemini CLI on an agent that runs another harness replaces its harness and requires a model. Set at most one of claude_code, codex, or gemini.",
5976
5987
  )
5988
+ idempotency_key: Union[str, None] = Field(
5989
+ default=None,
5990
+ description="Optional retry safety key, unique per account across POST /v1/sessions and POST /v1/agents/{id}/sessions. A retry with the same key returns the session the first request started instead of starting another. Only a request that started a session holds its key. A key is kept for 90 days. The SDKs make a key for each call that sets none, so their own retries never start a second session.",
5991
+ )
5977
5992
  images: Union[list[ImageAttachment], None] = Field(
5978
5993
  default=None,
5979
5994
  description="Images attached to the first message, base64 inline. Requires the model's `images` capability, listed by `/v1/account/models`. Each becomes an `[Image #N]` placeholder in the prompt and an image block the model sees with it. Up to 10, 5 MiB each; PNG, JPEG, GIF, or WebP.",
@@ -5982,9 +5997,9 @@ class StartAgentSessionRequest(EllipsisModel):
5982
5997
  default=None,
5983
5998
  description="The typed payload for an agent that declares input.json_schema. Required when the agent declares a schema; rejected when it does not. Validated against the schema and rendered through input.message, or as formatted JSON if no template is set, and added after the prompt. The rendered message must be non-empty.",
5984
5999
  )
5985
- metadata: Union[dict[str, Union[str, None]], None] = Field(
6000
+ metadata: Union[ConfigMetadataPatch, None] = Field(
5986
6001
  default=None,
5987
- description="Metadata merged into the agent's for this session. Each key you send sets that key, a `null` value removes the agent's key, and keys you leave out keep the agent's values.",
6002
+ description="Metadata merged into the agent's for this session. Labels you send are added to the agent's. Each annotation key you send sets that key, a `null` value removes the agent's key, and keys you leave out keep the agent's values.",
5988
6003
  )
5989
6004
  output: Union[OutputConfig, None] = Field(
5990
6005
  default=None,
@@ -6062,13 +6077,17 @@ class StartSessionRequest(EllipsisModel):
6062
6077
  default=None,
6063
6078
  description="Gemini CLI input and native options. Set exactly one of claude_code, codex, or gemini.",
6064
6079
  )
6080
+ idempotency_key: Union[str, None] = Field(
6081
+ default=None,
6082
+ description="Optional retry safety key, unique per account across POST /v1/sessions and POST /v1/agents/{id}/sessions. A retry with the same key returns the session the first request started instead of starting another. Only a request that started a session holds its key. A key is kept for 90 days. The SDKs make a key for each call that sets none, so their own retries never start a second session.",
6083
+ )
6065
6084
  images: Union[list[ImageAttachment], None] = Field(
6066
6085
  default=None,
6067
6086
  description="Images attached to the first message, base64 inline. Requires the model's `images` capability, listed by `/v1/account/models`. Each becomes an `[Image #N]` placeholder in the prompt and an image block the model sees with it. Up to 10, 5 MiB each; PNG, JPEG, GIF, or WebP.",
6068
6087
  )
6069
- metadata: Union[dict[str, str], None] = Field(
6088
+ metadata: Union[ConfigMetadata_Input, None] = Field(
6070
6089
  default=None,
6071
- description="Arbitrary string key/value metadata stored on the session.",
6090
+ description="Labels and annotations stored on the session, to filter sessions by.",
6072
6091
  )
6073
6092
  output: Union[OutputConfig, None] = Field(
6074
6093
  default=None,
@@ -6466,6 +6485,7 @@ __all__ = [
6466
6485
  "ConfigManagedBy",
6467
6486
  "ConfigMetadata_Input",
6468
6487
  "ConfigMetadata_Output",
6488
+ "ConfigMetadataPatch",
6469
6489
  "ConfigSpendWindow",
6470
6490
  "ConfigUsageBucket",
6471
6491
  "Cost",
File without changes
File without changes