ellipsis-dev 0.44.0__tar.gz → 0.46.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.44.0
3
+ Version: 0.46.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.44.0"
7
+ version = "0.46.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"
@@ -25,6 +25,7 @@ from ._transport import (
25
25
  Transport,
26
26
  build_body,
27
27
  build_query,
28
+ new_idempotency_key,
28
29
  )
29
30
 
30
31
 
@@ -815,6 +816,7 @@ class _SyncAgents:
815
816
  environment: Union[str, models.EnvironmentConfig_Input, None] = None,
816
817
  force_rebuild: Union[bool, None] = None,
817
818
  gemini: Union[models.GeminiOverrides, None] = None,
819
+ idempotency_key: Union[str, None] = None,
818
820
  images: Union[List[models.ImageAttachment], None] = None,
819
821
  input: Union[dict[str, Any], None] = None,
820
822
  metadata: Union[dict[str, Union[str, None]], None] = None,
@@ -854,6 +856,9 @@ class _SyncAgents:
854
856
  "environment": environment,
855
857
  "force_rebuild": force_rebuild,
856
858
  "gemini": gemini,
859
+ "idempotency_key": idempotency_key
860
+ if idempotency_key is not None
861
+ else new_idempotency_key(),
857
862
  "images": images,
858
863
  "input": input,
859
864
  "metadata": metadata,
@@ -1809,64 +1814,6 @@ class _SyncSessions(SyncSessionsSugar):
1809
1814
  def __init__(self, transport: Transport) -> None:
1810
1815
  self._transport = transport
1811
1816
 
1812
- def analytics(
1813
- self,
1814
- id: str,
1815
- ) -> models.SessionAnalytics:
1816
- """Get Session Analytics
1817
-
1818
- Measured tokens and spend over time, plus estimated content cost.
1819
-
1820
- Token lanes use the request's saved rates. Content attribution
1821
- apportions input/cache spend by text size, counting carried context
1822
- on every request. It is an estimate, not a tool call fee.
1823
- Only the latest 200 retained request bodies are inspected; missing
1824
- or unsupported context remains explicitly unattributed. No raw
1825
- prompts or tool results are returned. Session tokens may read only
1826
- their own session's analytics.
1827
-
1828
- Required permissions: read:sessions.
1829
- API key: Supported.
1830
- CLI user token: Supported.
1831
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Only the token's own session.
1832
- Higher permission levels include lower levels: read < write < delete.
1833
- Grant match patterns can further restrict access to individual resources.
1834
- """
1835
- path = f"/v1/sessions/{_quote(id)}/analytics"
1836
- response = self._transport.request(
1837
- "GET", path, response_model=models.SessionAnalytics
1838
- )
1839
- assert response is not None
1840
- return response
1841
-
1842
- def diff(
1843
- self,
1844
- id: str,
1845
- ) -> models.SessionDiffResponse:
1846
- """Get Session Diff
1847
-
1848
- Return the session's uncommitted changes as a patch.
1849
-
1850
- One unified-diff section per changed file, captured from the
1851
- environment's working tree when the session ended. The per-file
1852
- counts ride on the session itself (`git.repos[].files`); this
1853
- is the text behind them. Empty when the tree was clean. A
1854
- session token may read only its own session's diff.
1855
-
1856
- Required permissions: read:sessions.
1857
- API key: Supported.
1858
- CLI user token: Supported.
1859
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Only the token's own session.
1860
- Higher permission levels include lower levels: read < write < delete.
1861
- Grant match patterns can further restrict access to individual resources.
1862
- """
1863
- path = f"/v1/sessions/{_quote(id)}/diff"
1864
- response = self._transport.request(
1865
- "GET", path, response_model=models.SessionDiffResponse
1866
- )
1867
- assert response is not None
1868
- return response
1869
-
1870
1817
  def download(
1871
1818
  self,
1872
1819
  id: str,
@@ -1927,31 +1874,6 @@ class _SyncSessions(SyncSessionsSugar):
1927
1874
  assert response is not None
1928
1875
  return response
1929
1876
 
1930
- def git(
1931
- self,
1932
- id: str,
1933
- ) -> models.SessionGitResponse:
1934
- """Get Session Git
1935
-
1936
- The session's GitHub-side git view: its commits, and each PR it
1937
- opened joined to the live gh_prs row and reviews. The identity list
1938
- (output_git) rides the session itself over the WebSocket; this is
1939
- the join a "run" notify tells an open pane to refetch.
1940
-
1941
- Required permissions: read:sessions.
1942
- API key: Supported.
1943
- CLI user token: Supported.
1944
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration.
1945
- Higher permission levels include lower levels: read < write < delete.
1946
- Grant match patterns can further restrict access to individual resources.
1947
- """
1948
- path = f"/v1/sessions/{_quote(id)}/git"
1949
- response = self._transport.request(
1950
- "GET", path, response_model=models.SessionGitResponse
1951
- )
1952
- assert response is not None
1953
- return response
1954
-
1955
1877
  def image(
1956
1878
  self,
1957
1879
  session_id: str,
@@ -2072,42 +1994,6 @@ class _SyncSessions(SyncSessionsSugar):
2072
1994
  assert response is not None
2073
1995
  return response
2074
1996
 
2075
- def proxy(
2076
- self,
2077
- id: str,
2078
- *,
2079
- limit: Union[int, None] = None,
2080
- cursor: Union[str, None] = None,
2081
- ) -> SyncPage[models.ProxyRequest, models.SessionProxyResponse]:
2082
- """Get Session Proxy
2083
-
2084
- The requests the session's environment sent to GitHub through
2085
- Ellipsis, newest first. Each is one `proxy_requests` row as stored.
2086
-
2087
- Required permissions: read:sessions.
2088
- API key: Supported.
2089
- CLI user token: Supported.
2090
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Only the token's own session.
2091
- Higher permission levels include lower levels: read < write < delete.
2092
- Grant match patterns can further restrict access to individual resources.
2093
- """
2094
- path = f"/v1/sessions/{_quote(id)}/proxy"
2095
- query = build_query(
2096
- {
2097
- "limit": limit,
2098
- "cursor": cursor,
2099
- }
2100
- )
2101
- response = self._transport.request(
2102
- "GET", path, response_model=models.SessionProxyResponse, query=query
2103
- )
2104
- assert response is not None
2105
- return SyncPage(
2106
- response,
2107
- "proxy_requests",
2108
- lambda _cursor: self.proxy(id=id, limit=limit, cursor=_cursor),
2109
- )
2110
-
2111
1997
  def records(
2112
1998
  self,
2113
1999
  id: str,
@@ -2179,7 +2065,9 @@ class _SyncSessions(SyncSessionsSugar):
2179
2065
  """
2180
2066
  path = f"/v1/sessions/{_quote(id)}/messages"
2181
2067
  body_fields: dict[str, Any] = {
2182
- "idempotency_key": idempotency_key,
2068
+ "idempotency_key": idempotency_key
2069
+ if idempotency_key is not None
2070
+ else new_idempotency_key(),
2183
2071
  "images": images,
2184
2072
  "message": message,
2185
2073
  }
@@ -2224,6 +2112,7 @@ class _SyncSessions(SyncSessionsSugar):
2224
2112
  environment: Union[str, models.EnvironmentConfig_Input, None] = None,
2225
2113
  force_rebuild: Union[bool, None] = None,
2226
2114
  gemini: Union[models.GeminiConfig_Input, None] = None,
2115
+ idempotency_key: Union[str, None] = None,
2227
2116
  images: Union[List[models.ImageAttachment], None] = None,
2228
2117
  metadata: Union[dict[str, str], None] = None,
2229
2118
  output: Union[models.OutputConfig, None] = None,
@@ -2263,6 +2152,9 @@ class _SyncSessions(SyncSessionsSugar):
2263
2152
  "environment": environment,
2264
2153
  "force_rebuild": force_rebuild,
2265
2154
  "gemini": gemini,
2155
+ "idempotency_key": idempotency_key
2156
+ if idempotency_key is not None
2157
+ else new_idempotency_key(),
2266
2158
  "images": images,
2267
2159
  "metadata": metadata,
2268
2160
  "output": output,
@@ -2325,6 +2217,35 @@ class _SyncSessions(SyncSessionsSugar):
2325
2217
  assert response is not None
2326
2218
  return response
2327
2219
 
2220
+ def timeline(
2221
+ self,
2222
+ id: str,
2223
+ ) -> models.SessionTimelineResponse:
2224
+ """Get Session Timeline
2225
+
2226
+ What the session did, newest first. Each time its environment
2227
+ started preparing, became ready, fell asleep after its idle window,
2228
+ and woke for the next message, its close, and what it did on
2229
+ GitHub through Ellipsis, such as the repositories it cloned, its pushes,
2230
+ what it did to pull requests and issues, the files it committed
2231
+ through the API, releases, and workflow runs, read from the
2232
+ requests its environment sent. A run of the same GitHub action is
2233
+ one entry.
2234
+
2235
+ Required permissions: read:sessions.
2236
+ API key: Supported.
2237
+ CLI user token: Supported.
2238
+ Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Only the token's own session.
2239
+ Higher permission levels include lower levels: read < write < delete.
2240
+ Grant match patterns can further restrict access to individual resources.
2241
+ """
2242
+ path = f"/v1/sessions/{_quote(id)}/timeline"
2243
+ response = self._transport.request(
2244
+ "GET", path, response_model=models.SessionTimelineResponse
2245
+ )
2246
+ assert response is not None
2247
+ return response
2248
+
2328
2249
  def update(
2329
2250
  self,
2330
2251
  id: str,
@@ -2344,9 +2265,9 @@ class _SyncSessions(SyncSessionsSugar):
2344
2265
  thread, pull request, issue, or Linear issue it answers starts a
2345
2266
  new session. A session is also archived when it closes and after
2346
2267
  the account's `archive_inactive_sessions_days` without a message.
2347
- When the account sets session log retention, the session's
2348
- records, messages, and prompt are deleted that many days after it
2349
- was archived (`archived.deletes_at`). The session itself stays.
2268
+ The session's records, messages, and prompt are deleted the
2269
+ account's `session_logs_days` after it was archived
2270
+ (`archived.deletes_at`). The session itself stays.
2350
2271
 
2351
2272
  Set archived to false to clear both fields and let the session
2352
2273
  take messages again. 409 `session_superseded` when a newer session
@@ -2735,7 +2656,7 @@ class _SyncWebhooksDeliveries:
2735
2656
  List webhook deliveries, newest first.
2736
2657
 
2737
2658
  Includes verification pings and deliveries for deleted webhooks.
2738
- History is retained for 30 days. Retries belong to the same delivery.
2659
+ History is kept for 7 days. Retries belong to the same delivery.
2739
2660
 
2740
2661
  Required permissions: read:webhooks.
2741
2662
  API key: Supported.
@@ -2830,7 +2751,7 @@ class _SyncWebhooks:
2830
2751
  Delete a webhook and cancel outstanding deliveries.
2831
2752
 
2832
2753
  An HTTP request already in flight may finish. Delivery history remains
2833
- available for 30 days. Deletion frees a webhook slot.
2754
+ available for 7 days. Deletion frees a webhook slot.
2834
2755
 
2835
2756
  Required permissions: delete:webhooks.
2836
2757
  API key: Supported.
@@ -3797,6 +3718,7 @@ class _AsyncAgents:
3797
3718
  environment: Union[str, models.EnvironmentConfig_Input, None] = None,
3798
3719
  force_rebuild: Union[bool, None] = None,
3799
3720
  gemini: Union[models.GeminiOverrides, None] = None,
3721
+ idempotency_key: Union[str, None] = None,
3800
3722
  images: Union[List[models.ImageAttachment], None] = None,
3801
3723
  input: Union[dict[str, Any], None] = None,
3802
3724
  metadata: Union[dict[str, Union[str, None]], None] = None,
@@ -3836,6 +3758,9 @@ class _AsyncAgents:
3836
3758
  "environment": environment,
3837
3759
  "force_rebuild": force_rebuild,
3838
3760
  "gemini": gemini,
3761
+ "idempotency_key": idempotency_key
3762
+ if idempotency_key is not None
3763
+ else new_idempotency_key(),
3839
3764
  "images": images,
3840
3765
  "input": input,
3841
3766
  "metadata": metadata,
@@ -4793,64 +4718,6 @@ class _AsyncSessions(AsyncSessionsSugar):
4793
4718
  def __init__(self, transport: AsyncTransport) -> None:
4794
4719
  self._transport = transport
4795
4720
 
4796
- async def analytics(
4797
- self,
4798
- id: str,
4799
- ) -> models.SessionAnalytics:
4800
- """Get Session Analytics
4801
-
4802
- Measured tokens and spend over time, plus estimated content cost.
4803
-
4804
- Token lanes use the request's saved rates. Content attribution
4805
- apportions input/cache spend by text size, counting carried context
4806
- on every request. It is an estimate, not a tool call fee.
4807
- Only the latest 200 retained request bodies are inspected; missing
4808
- or unsupported context remains explicitly unattributed. No raw
4809
- prompts or tool results are returned. Session tokens may read only
4810
- their own session's analytics.
4811
-
4812
- Required permissions: read:sessions.
4813
- API key: Supported.
4814
- CLI user token: Supported.
4815
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Only the token's own session.
4816
- Higher permission levels include lower levels: read < write < delete.
4817
- Grant match patterns can further restrict access to individual resources.
4818
- """
4819
- path = f"/v1/sessions/{_quote(id)}/analytics"
4820
- response = await self._transport.request(
4821
- "GET", path, response_model=models.SessionAnalytics
4822
- )
4823
- assert response is not None
4824
- return response
4825
-
4826
- async def diff(
4827
- self,
4828
- id: str,
4829
- ) -> models.SessionDiffResponse:
4830
- """Get Session Diff
4831
-
4832
- Return the session's uncommitted changes as a patch.
4833
-
4834
- One unified-diff section per changed file, captured from the
4835
- environment's working tree when the session ended. The per-file
4836
- counts ride on the session itself (`git.repos[].files`); this
4837
- is the text behind them. Empty when the tree was clean. A
4838
- session token may read only its own session's diff.
4839
-
4840
- Required permissions: read:sessions.
4841
- API key: Supported.
4842
- CLI user token: Supported.
4843
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Only the token's own session.
4844
- Higher permission levels include lower levels: read < write < delete.
4845
- Grant match patterns can further restrict access to individual resources.
4846
- """
4847
- path = f"/v1/sessions/{_quote(id)}/diff"
4848
- response = await self._transport.request(
4849
- "GET", path, response_model=models.SessionDiffResponse
4850
- )
4851
- assert response is not None
4852
- return response
4853
-
4854
4721
  async def download(
4855
4722
  self,
4856
4723
  id: str,
@@ -4911,31 +4778,6 @@ class _AsyncSessions(AsyncSessionsSugar):
4911
4778
  assert response is not None
4912
4779
  return response
4913
4780
 
4914
- async def git(
4915
- self,
4916
- id: str,
4917
- ) -> models.SessionGitResponse:
4918
- """Get Session Git
4919
-
4920
- The session's GitHub-side git view: its commits, and each PR it
4921
- opened joined to the live gh_prs row and reviews. The identity list
4922
- (output_git) rides the session itself over the WebSocket; this is
4923
- the join a "run" notify tells an open pane to refetch.
4924
-
4925
- Required permissions: read:sessions.
4926
- API key: Supported.
4927
- CLI user token: Supported.
4928
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration.
4929
- Higher permission levels include lower levels: read < write < delete.
4930
- Grant match patterns can further restrict access to individual resources.
4931
- """
4932
- path = f"/v1/sessions/{_quote(id)}/git"
4933
- response = await self._transport.request(
4934
- "GET", path, response_model=models.SessionGitResponse
4935
- )
4936
- assert response is not None
4937
- return response
4938
-
4939
4781
  async def image(
4940
4782
  self,
4941
4783
  session_id: str,
@@ -5056,42 +4898,6 @@ class _AsyncSessions(AsyncSessionsSugar):
5056
4898
  assert response is not None
5057
4899
  return response
5058
4900
 
5059
- async def proxy(
5060
- self,
5061
- id: str,
5062
- *,
5063
- limit: Union[int, None] = None,
5064
- cursor: Union[str, None] = None,
5065
- ) -> AsyncPage[models.ProxyRequest, models.SessionProxyResponse]:
5066
- """Get Session Proxy
5067
-
5068
- The requests the session's environment sent to GitHub through
5069
- Ellipsis, newest first. Each is one `proxy_requests` row as stored.
5070
-
5071
- Required permissions: read:sessions.
5072
- API key: Supported.
5073
- CLI user token: Supported.
5074
- Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Only the token's own session.
5075
- Higher permission levels include lower levels: read < write < delete.
5076
- Grant match patterns can further restrict access to individual resources.
5077
- """
5078
- path = f"/v1/sessions/{_quote(id)}/proxy"
5079
- query = build_query(
5080
- {
5081
- "limit": limit,
5082
- "cursor": cursor,
5083
- }
5084
- )
5085
- response = await self._transport.request(
5086
- "GET", path, response_model=models.SessionProxyResponse, query=query
5087
- )
5088
- assert response is not None
5089
- return AsyncPage(
5090
- response,
5091
- "proxy_requests",
5092
- lambda _cursor: self.proxy(id=id, limit=limit, cursor=_cursor),
5093
- )
5094
-
5095
4901
  async def records(
5096
4902
  self,
5097
4903
  id: str,
@@ -5163,7 +4969,9 @@ class _AsyncSessions(AsyncSessionsSugar):
5163
4969
  """
5164
4970
  path = f"/v1/sessions/{_quote(id)}/messages"
5165
4971
  body_fields: dict[str, Any] = {
5166
- "idempotency_key": idempotency_key,
4972
+ "idempotency_key": idempotency_key
4973
+ if idempotency_key is not None
4974
+ else new_idempotency_key(),
5167
4975
  "images": images,
5168
4976
  "message": message,
5169
4977
  }
@@ -5208,6 +5016,7 @@ class _AsyncSessions(AsyncSessionsSugar):
5208
5016
  environment: Union[str, models.EnvironmentConfig_Input, None] = None,
5209
5017
  force_rebuild: Union[bool, None] = None,
5210
5018
  gemini: Union[models.GeminiConfig_Input, None] = None,
5019
+ idempotency_key: Union[str, None] = None,
5211
5020
  images: Union[List[models.ImageAttachment], None] = None,
5212
5021
  metadata: Union[dict[str, str], None] = None,
5213
5022
  output: Union[models.OutputConfig, None] = None,
@@ -5247,6 +5056,9 @@ class _AsyncSessions(AsyncSessionsSugar):
5247
5056
  "environment": environment,
5248
5057
  "force_rebuild": force_rebuild,
5249
5058
  "gemini": gemini,
5059
+ "idempotency_key": idempotency_key
5060
+ if idempotency_key is not None
5061
+ else new_idempotency_key(),
5250
5062
  "images": images,
5251
5063
  "metadata": metadata,
5252
5064
  "output": output,
@@ -5309,6 +5121,35 @@ class _AsyncSessions(AsyncSessionsSugar):
5309
5121
  assert response is not None
5310
5122
  return response
5311
5123
 
5124
+ async def timeline(
5125
+ self,
5126
+ id: str,
5127
+ ) -> models.SessionTimelineResponse:
5128
+ """Get Session Timeline
5129
+
5130
+ What the session did, newest first. Each time its environment
5131
+ started preparing, became ready, fell asleep after its idle window,
5132
+ and woke for the next message, its close, and what it did on
5133
+ GitHub through Ellipsis, such as the repositories it cloned, its pushes,
5134
+ what it did to pull requests and issues, the files it committed
5135
+ through the API, releases, and workflow runs, read from the
5136
+ requests its environment sent. A run of the same GitHub action is
5137
+ one entry.
5138
+
5139
+ Required permissions: read:sessions.
5140
+ API key: Supported.
5141
+ CLI user token: Supported.
5142
+ Session token: Requires the ellipsis:api scope and the required grants in the token's permissions.ellipsis configuration. Only the token's own session.
5143
+ Higher permission levels include lower levels: read < write < delete.
5144
+ Grant match patterns can further restrict access to individual resources.
5145
+ """
5146
+ path = f"/v1/sessions/{_quote(id)}/timeline"
5147
+ response = await self._transport.request(
5148
+ "GET", path, response_model=models.SessionTimelineResponse
5149
+ )
5150
+ assert response is not None
5151
+ return response
5152
+
5312
5153
  async def update(
5313
5154
  self,
5314
5155
  id: str,
@@ -5328,9 +5169,9 @@ class _AsyncSessions(AsyncSessionsSugar):
5328
5169
  thread, pull request, issue, or Linear issue it answers starts a
5329
5170
  new session. A session is also archived when it closes and after
5330
5171
  the account's `archive_inactive_sessions_days` without a message.
5331
- When the account sets session log retention, the session's
5332
- records, messages, and prompt are deleted that many days after it
5333
- was archived (`archived.deletes_at`). The session itself stays.
5172
+ The session's records, messages, and prompt are deleted the
5173
+ account's `session_logs_days` after it was archived
5174
+ (`archived.deletes_at`). The session itself stays.
5334
5175
 
5335
5176
  Set archived to false to clear both fields and let the session
5336
5177
  take messages again. 409 `session_superseded` when a newer session
@@ -5719,7 +5560,7 @@ class _AsyncWebhooksDeliveries:
5719
5560
  List webhook deliveries, newest first.
5720
5561
 
5721
5562
  Includes verification pings and deliveries for deleted webhooks.
5722
- History is retained for 30 days. Retries belong to the same delivery.
5563
+ History is kept for 7 days. Retries belong to the same delivery.
5723
5564
 
5724
5565
  Required permissions: read:webhooks.
5725
5566
  API key: Supported.
@@ -5814,7 +5655,7 @@ class _AsyncWebhooks:
5814
5655
  Delete a webhook and cancel outstanding deliveries.
5815
5656
 
5816
5657
  An HTTP request already in flight may finish. Delivery history remains
5817
- available for 30 days. Deletion frees a webhook slot.
5658
+ available for 7 days. Deletion frees a webhook slot.
5818
5659
 
5819
5660
  Required permissions: delete:webhooks.
5820
5661
  API key: Supported.
@@ -117,6 +117,7 @@ def parse_frame(data: str | bytes) -> frames.StreamFrame | None:
117
117
 
118
118
  _FRAME_TYPES: dict[Any, Any] = {
119
119
  "session": frames.SessionFrame,
120
+ "timeline": frames.TimelineFrame,
120
121
  "records": frames.RecordsFrame,
121
122
  "delta": frames.DeltaFrame,
122
123
  "heartbeat": frames.HeartbeatFrame,
@@ -7,6 +7,7 @@ model; everything HTTP lives here.
7
7
 
8
8
  import random
9
9
  import time
10
+ import uuid
10
11
  from typing import Any, TypeVar
11
12
 
12
13
  import httpx
@@ -56,6 +57,13 @@ def build_body(body: BaseModel) -> Any:
56
57
  return body.model_dump(mode="json", by_alias=True, exclude_none=True)
57
58
 
58
59
 
60
+ def new_idempotency_key() -> str:
61
+ """A fresh key for a request that takes `idempotency_key`. The generated
62
+ client makes one per call when the caller sets none, so the transport's
63
+ retries of that call reuse it and the server applies the call once."""
64
+ return uuid.uuid4().hex
65
+
66
+
59
67
  def _backoff_seconds(attempt: int, retry_after: str | None) -> float:
60
68
  if retry_after is not None:
61
69
  try:
@@ -75,12 +75,37 @@ class SessionFrame(EllipsisModel):
75
75
  type: Literal["session"]
76
76
 
77
77
 
78
+ class TimelineFrame(EllipsisModel):
79
+ """The session's whole timeline, newest first: sent after the first session
80
+ frame, and resent whole whenever it changes. The same events
81
+ `GET /v1/sessions/{id}/timeline` returns.
82
+ """
83
+
84
+ events: list[
85
+ Union[
86
+ models.EnvironmentTimelineEvent,
87
+ models.SessionClosedTimelineEvent,
88
+ models.GithubCloneTimelineEvent,
89
+ models.GithubPushTimelineEvent,
90
+ models.GithubCommitTimelineEvent,
91
+ models.GithubPullRequestTimelineEvent,
92
+ models.GithubIssueTimelineEvent,
93
+ models.GithubReleaseTimelineEvent,
94
+ models.GithubWorkflowTimelineEvent,
95
+ models.GithubRefusedTimelineEvent,
96
+ models.GithubOtherTimelineEvent,
97
+ ]
98
+ ] = Field(description="What the session did, newest first.")
99
+ type: Literal["timeline"]
100
+
101
+
78
102
  StreamFrame = Union[
79
103
  DeltaFrame,
80
104
  ErrorFrame,
81
105
  HeartbeatFrame,
82
106
  RecordsFrame,
83
107
  SessionFrame,
108
+ TimelineFrame,
84
109
  ]
85
110
  """One server->client frame. Unknown frame types must be ignored
86
111
  (additive server changes are not a protocol break)."""
@@ -92,5 +117,6 @@ __all__ = [
92
117
  "HeartbeatFrame",
93
118
  "RecordsFrame",
94
119
  "SessionFrame",
120
+ "TimelineFrame",
95
121
  "StreamFrame",
96
122
  ]