@toshicloud/sdk 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +82 -40
  2. package/dist/client.d.ts +33 -2
  3. package/dist/client.js +28 -3
  4. package/dist/errors.d.ts +74 -22
  5. package/dist/errors.js +99 -27
  6. package/dist/gen/toshi/permission_pb.d.ts +207 -2
  7. package/dist/gen/toshi/permission_pb.js +54 -2
  8. package/dist/gen/toshi/workspace/v1/account_pb.d.ts +590 -0
  9. package/dist/gen/toshi/workspace/v1/account_pb.js +75 -0
  10. package/dist/gen/toshi/workspace/v1/artifacts_pb.d.ts +14 -8
  11. package/dist/gen/toshi/workspace/v1/artifacts_pb.js +9 -7
  12. package/dist/gen/toshi/workspace/v1/audit_events_pb.d.ts +191 -6
  13. package/dist/gen/toshi/workspace/v1/audit_events_pb.js +11 -1
  14. package/dist/gen/toshi/workspace/v1/channels_pb.d.ts +1194 -21
  15. package/dist/gen/toshi/workspace/v1/channels_pb.js +185 -39
  16. package/dist/gen/toshi/workspace/v1/connections_pb.d.ts +1481 -0
  17. package/dist/gen/toshi/workspace/v1/connections_pb.js +208 -0
  18. package/dist/gen/toshi/workspace/v1/discovery_pb.d.ts +291 -43
  19. package/dist/gen/toshi/workspace/v1/discovery_pb.js +79 -30
  20. package/dist/gen/toshi/workspace/v1/display_pb.d.ts +198 -15
  21. package/dist/gen/toshi/workspace/v1/display_pb.js +52 -2
  22. package/dist/gen/toshi/workspace/v1/domains_pb.d.ts +864 -0
  23. package/dist/gen/toshi/workspace/v1/domains_pb.js +152 -0
  24. package/dist/gen/toshi/workspace/v1/fillers_pb.d.ts +12 -4
  25. package/dist/gen/toshi/workspace/v1/fillers_pb.js +1 -1
  26. package/dist/gen/toshi/workspace/v1/grants_pb.d.ts +1297 -0
  27. package/dist/gen/toshi/workspace/v1/grants_pb.js +271 -0
  28. package/dist/gen/toshi/workspace/v1/hostjobs_pb.d.ts +315 -15
  29. package/dist/gen/toshi/workspace/v1/hostjobs_pb.js +10 -5
  30. package/dist/gen/toshi/workspace/v1/hosts_pb.d.ts +196 -7
  31. package/dist/gen/toshi/workspace/v1/hosts_pb.js +12 -7
  32. package/dist/gen/toshi/workspace/v1/images_pb.d.ts +1463 -0
  33. package/dist/gen/toshi/workspace/v1/images_pb.js +263 -0
  34. package/dist/gen/toshi/workspace/v1/products_pb.d.ts +99 -296
  35. package/dist/gen/toshi/workspace/v1/products_pb.js +7 -53
  36. package/dist/gen/toshi/workspace/v1/projects_pb.d.ts +1474 -99
  37. package/dist/gen/toshi/workspace/v1/projects_pb.js +150 -33
  38. package/dist/gen/toshi/workspace/v1/recordings_pb.d.ts +181 -14
  39. package/dist/gen/toshi/workspace/v1/recordings_pb.js +54 -14
  40. package/dist/gen/toshi/workspace/v1/roles_pb.d.ts +1994 -0
  41. package/dist/gen/toshi/workspace/v1/roles_pb.js +355 -0
  42. package/dist/gen/toshi/workspace/v1/secrets_pb.d.ts +314 -8
  43. package/dist/gen/toshi/workspace/v1/secrets_pb.js +42 -18
  44. package/dist/gen/toshi/workspace/v1/storage_providers_pb.d.ts +14 -0
  45. package/dist/gen/toshi/workspace/v1/storage_providers_pb.js +1 -1
  46. package/dist/gen/toshi/workspace/v1/tokens_pb.d.ts +434 -5
  47. package/dist/gen/toshi/workspace/v1/tokens_pb.js +65 -8
  48. package/dist/gen/toshi/workspace/v1/triggers_pb.d.ts +1099 -0
  49. package/dist/gen/toshi/workspace/v1/triggers_pb.js +185 -0
  50. package/dist/gen/toshi/workspace/v1/volumes_pb.d.ts +1052 -33
  51. package/dist/gen/toshi/workspace/v1/volumes_pb.js +169 -11
  52. package/dist/gen/toshi/workspace/v1/webhook_pb.d.ts +66 -8
  53. package/dist/gen/toshi/workspace/v1/webhook_pb.js +4 -4
  54. package/dist/gen/toshi/workspace/v1/workloads_pb.d.ts +2043 -54
  55. package/dist/gen/toshi/workspace/v1/workloads_pb.js +204 -54
  56. package/dist/gen/toshi/workspace/v1/workspace_pb.d.ts +6290 -937
  57. package/dist/gen/toshi/workspace/v1/workspace_pb.js +801 -88
  58. package/dist/gen/toshi/workspace/v1/workspace_service_pb.d.ts +2176 -630
  59. package/dist/gen/toshi/workspace/v1/workspace_service_pb.js +191 -74
  60. package/dist/index.d.ts +8 -1
  61. package/dist/index.js +8 -1
  62. package/dist/interceptors.d.ts +6 -2
  63. package/dist/interceptors.js +13 -8
  64. package/dist/retry.d.ts +18 -0
  65. package/dist/retry.js +225 -23
  66. package/dist/test/procedures.js +100 -5
  67. package/dist/version.d.ts +1 -1
  68. package/dist/version.js +1 -1
  69. package/dist/wait.d.ts +4 -0
  70. package/dist/wait.js +24 -1
  71. package/package.json +3 -2
@@ -1,7 +1,7 @@
1
1
  import { ScalarType } from "@bufbuild/protobuf";
2
2
  import { Code, ConnectError } from "@connectrpc/connect";
3
- import { backoffDelay, isTransportLoss, newRequestKey, procedurePath, retryClassFor, sleep, } from "./retry.js";
4
- import { asProductError } from "./errors.js";
3
+ import { isTransportLoss, newRequestKey, procedurePath, retryClassFor, retryDelayMs, serverWaitMs, sleep, } from "./retry.js";
4
+ import { asTypedError } from "./errors.js";
5
5
  /**
6
6
  * Sends `Authorization: Bearer <token>` on every call and stream, on a copy of
7
7
  * the request: it is the innermost interceptor, so none outside it sees the
@@ -79,7 +79,11 @@ export function keyInterceptor() {
79
79
  return next(req);
80
80
  };
81
81
  }
82
- /** Retries a unary read or keyed write after a transport loss, with full jitter (retry.go:98-186). */
82
+ /**
83
+ * Retries a unary read or keyed write after a transport loss, with full jitter
84
+ * (retry.go:98-186), or after a refusal that names how long to wait
85
+ * (serverWaitMs), waiting at least that long.
86
+ */
83
87
  export function retryInterceptor(retry) {
84
88
  if (retry === false) {
85
89
  return (next) => next;
@@ -96,10 +100,11 @@ export function retryInterceptor(retry) {
96
100
  return await next(req);
97
101
  }
98
102
  catch (err) {
99
- if (attempt + 1 >= policy.maxAttempts || !isTransportLoss(err)) {
103
+ const waitMs = serverWaitMs(err);
104
+ if (attempt + 1 >= policy.maxAttempts || (!isTransportLoss(err) && waitMs === undefined)) {
100
105
  throw err;
101
106
  }
102
- await sleep(backoffDelay(policy, attempt), req.signal);
107
+ await sleep(retryDelayMs(policy, attempt, waitMs), req.signal);
103
108
  }
104
109
  }
105
110
  };
@@ -199,14 +204,14 @@ function isShared(msg, desc) {
199
204
  * on a stream's messages. It is the outermost interceptor, so the retry and
200
205
  * the helpers inside see the ConnectError they always did.
201
206
  */
202
- export function productInterceptor() {
207
+ export function refusalInterceptor() {
203
208
  return (next) => async (req) => {
204
209
  let res;
205
210
  try {
206
211
  res = await next(req);
207
212
  }
208
213
  catch (err) {
209
- throw asProductError(err);
214
+ throw asTypedError(err);
210
215
  }
211
216
  if (!res.stream) {
212
217
  return res;
@@ -219,6 +224,6 @@ async function* productStream(messages) {
219
224
  yield* messages;
220
225
  }
221
226
  catch (err) {
222
- throw asProductError(err);
227
+ throw asTypedError(err);
223
228
  }
224
229
  }
package/dist/retry.d.ts CHANGED
@@ -34,6 +34,24 @@ export declare function isTransportLoss(err: unknown): boolean;
34
34
  export declare function isStreamLoss(err: unknown): boolean;
35
35
  /** A uniform jittered delay in [0, min(maxBackoffMs, initialBackoffMs * 2**attempt)]. */
36
36
  export declare function backoffDelay(policy: RetryPolicy, attempt: number): number;
37
+ /**
38
+ * How long the server asked the client to wait before a retry, in
39
+ * milliseconds, or undefined when it named no wait. A RESOURCE_EXHAUSTED
40
+ * refusal names the wait in a google.rpc.RetryInfo detail and in its
41
+ * Retry-After header, the detail winning when both are there; the API's rate
42
+ * limit sends both, with the same whole seconds. Such a refusal is retried
43
+ * only when it names a wait: one without, such as a plan's limit, does not
44
+ * pass on a retry. An HTTP 429 without a Connect error body reaches the
45
+ * client as Code.Unavailable with the message "HTTP 429" and the response's
46
+ * headers (connect 2.2.0 protocol-connect/validate-response.js:33), and names
47
+ * the wait in its Retry-After. Every other error names none.
48
+ */
49
+ export declare function serverWaitMs(err: unknown): number | undefined;
50
+ /**
51
+ * The delay before retry attempt + 1: the backoff's, or the server's wait when
52
+ * that is longer, never more than maxBackoffMs.
53
+ */
54
+ export declare function retryDelayMs(policy: RetryPolicy, attempt: number, waitMs: number | undefined): number;
37
55
  /** Sleeps, rejecting at once when the signal aborts (retry.go:186-200). */
38
56
  export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
39
57
  /** The signal's reason as an error, or a Canceled ConnectError. */
package/dist/retry.js CHANGED
@@ -1,14 +1,26 @@
1
1
  import { Code, ConnectError } from "@connectrpc/connect";
2
+ import { BinaryReader, WireType } from "@bufbuild/protobuf/wire";
2
3
  /** Five attempts, a 200 ms initial backoff and a 10 s cap (options.go:79-85). */
3
4
  export const defaultRetryPolicy = {
4
5
  maxAttempts: 5,
5
6
  initialBackoffMs: 200,
6
7
  maxBackoffMs: 10_000,
7
8
  };
8
- // One entry per procedure of the Workspace API's services. A call that changes
9
- // what exists carries a request key and is retried; a call with no key is
10
- // retried only when it reads; a stream is never retried here.
9
+ // One entry per procedure of the Workspace API's services. A stream is never
10
+ // retried here; a call whose request takes a request_key is retried with that
11
+ // key, so the server answers a retry with the first result; a read, a call
12
+ // whose permission's level is read, is retried too; every other call is not.
13
+ // test/retry.test.ts derives each class from the generated descriptors and
14
+ // fails when a regeneration moves a procedure, so an entry follows the API and
15
+ // not a list someone remembered to update. An entry whose class needs saying
16
+ // more about has it above the entry.
11
17
  const procedureRetryClass = {
18
+ "/toshi.workspace.v1.AccountService/GetAccount": "read",
19
+ "/toshi.workspace.v1.AccountService/ListIdentities": "read",
20
+ // A repeat answers NOT_FOUND ("an identity the caller does not have fails
21
+ // with NOT_FOUND"), so a retry would report failure for a call that worked;
22
+ // its permission's level is read although it changes state.
23
+ "/toshi.workspace.v1.AccountService/UnlinkIdentity": "none",
12
24
  "/toshi.workspace.v1.WorkloadService/CreateJob": "keyed",
13
25
  "/toshi.workspace.v1.WorkloadService/GetJob": "read",
14
26
  "/toshi.workspace.v1.WorkloadService/ListJobs": "read",
@@ -21,7 +33,10 @@ const procedureRetryClass = {
21
33
  "/toshi.workspace.v1.WorkloadService/WatchTelemetry": "none",
22
34
  "/toshi.workspace.v1.WorkloadService/WatchGuestLog": "none",
23
35
  "/toshi.workspace.v1.WorkloadService/Execute": "none",
36
+ "/toshi.workspace.v1.WorkloadService/ExecuteCommand": "none",
37
+ "/toshi.workspace.v1.WorkloadService/AnswerPrompt": "none",
24
38
  "/toshi.workspace.v1.WorkloadService/CancelExecution": "keyed",
39
+ "/toshi.workspace.v1.WorkloadService/ExportEvents": "none",
25
40
  "/toshi.workspace.v1.WorkloadService/GetOperation": "read",
26
41
  "/toshi.workspace.v1.WorkloadService/WaitOperation": "read",
27
42
  "/toshi.workspace.v1.WorkloadService/ListEvents": "read",
@@ -29,11 +44,16 @@ const procedureRetryClass = {
29
44
  "/toshi.workspace.v1.WorkloadService/CreatePreviewShareLink": "none",
30
45
  "/toshi.workspace.v1.WorkloadService/ListPreviewShareLinks": "read",
31
46
  "/toshi.workspace.v1.WorkloadService/RevokePreviewShareLink": "none",
47
+ "/toshi.workspace.v1.WorkloadService/CreateServiceShareLink": "none",
48
+ "/toshi.workspace.v1.WorkloadService/ListServiceShareLinks": "read",
49
+ "/toshi.workspace.v1.WorkloadService/RevokeServiceShareLink": "none",
32
50
  "/toshi.workspace.v1.WorkloadService/CreateSandbox": "keyed",
33
51
  "/toshi.workspace.v1.WorkloadService/GetSandbox": "read",
34
52
  "/toshi.workspace.v1.WorkloadService/ListSandboxes": "read",
35
53
  "/toshi.workspace.v1.WorkloadService/UpdateSandbox": "keyed",
36
54
  "/toshi.workspace.v1.WorkloadService/DeleteSandbox": "keyed",
55
+ "/toshi.workspace.v1.WorkloadService/StopSandbox": "keyed",
56
+ "/toshi.workspace.v1.WorkloadService/StartSandbox": "keyed",
37
57
  "/toshi.workspace.v1.WorkloadService/WaitSandbox": "read",
38
58
  "/toshi.workspace.v1.WorkloadService/WatchSandboxes": "none",
39
59
  "/toshi.workspace.v1.WorkloadService/CreateService": "keyed",
@@ -43,14 +63,30 @@ const procedureRetryClass = {
43
63
  "/toshi.workspace.v1.WorkloadService/DeleteService": "keyed",
44
64
  "/toshi.workspace.v1.WorkloadService/WaitService": "read",
45
65
  "/toshi.workspace.v1.WorkloadService/WatchServices": "none",
46
- "/toshi.workspace.v1.WorkloadService/StartService": "none",
47
- "/toshi.workspace.v1.WorkloadService/StopService": "none",
48
- "/toshi.workspace.v1.WorkloadService/SetServiceMode": "none",
49
- "/toshi.workspace.v1.WorkloadService/WakeService": "none",
50
- "/toshi.workspace.v1.WorkloadService/HoldService": "none",
51
- "/toshi.workspace.v1.WorkloadService/ReleaseService": "none",
66
+ "/toshi.workspace.v1.WorkloadService/StartService": "keyed",
67
+ "/toshi.workspace.v1.WorkloadService/StopService": "keyed",
68
+ "/toshi.workspace.v1.WorkloadService/SetServiceMode": "keyed",
69
+ "/toshi.workspace.v1.WorkloadService/WakeService": "keyed",
70
+ "/toshi.workspace.v1.WorkloadService/HoldService": "keyed",
71
+ "/toshi.workspace.v1.WorkloadService/ReleaseService": "keyed",
72
+ "/toshi.workspace.v1.WorkloadService/ListServiceRevisions": "read",
73
+ "/toshi.workspace.v1.WorkloadService/GetServiceRevision": "read",
74
+ "/toshi.workspace.v1.WorkloadService/RollbackService": "keyed",
75
+ "/toshi.workspace.v1.WorkloadService/ListServiceActivations": "read",
52
76
  "/toshi.workspace.v1.WorkloadService/ListServiceRuns": "read",
53
- "/toshi.workspace.v1.SecretService/CreateSecretSet": "none",
77
+ "/toshi.workspace.v1.WorkloadService/EmitWorkloadEvent": "none",
78
+ "/toshi.workspace.v1.WorkloadService/SetServiceAlarm": "none",
79
+ "/toshi.workspace.v1.WorkloadService/GetServiceAlarm": "read",
80
+ "/toshi.workspace.v1.WorkloadService/ListServiceAlarms": "read",
81
+ "/toshi.workspace.v1.WorkloadService/DeleteServiceAlarm": "none",
82
+ "/toshi.workspace.v1.WorkloadService/PauseServiceAlarm": "none",
83
+ "/toshi.workspace.v1.WorkloadService/ResumeServiceAlarm": "none",
84
+ "/toshi.workspace.v1.WorkloadService/RunServiceAlarmNow": "keyed",
85
+ "/toshi.workspace.v1.WorkloadService/StartServiceSignIn": "none",
86
+ "/toshi.workspace.v1.WorkloadService/AnswerServiceSignIn": "none",
87
+ "/toshi.workspace.v1.WorkloadService/CancelServiceSignIn": "none",
88
+ "/toshi.workspace.v1.WorkloadService/GetServiceSignInStatus": "read",
89
+ "/toshi.workspace.v1.SecretService/CreateSecretSet": "keyed",
54
90
  "/toshi.workspace.v1.SecretService/ListSecretSets": "read",
55
91
  "/toshi.workspace.v1.SecretService/DeleteSecretSet": "none",
56
92
  "/toshi.workspace.v1.SecretService/PutSecret": "none",
@@ -60,18 +96,28 @@ const procedureRetryClass = {
60
96
  "/toshi.workspace.v1.SecretService/GrantSecretSet": "none",
61
97
  "/toshi.workspace.v1.SecretService/RevokeSecretSet": "none",
62
98
  "/toshi.workspace.v1.SecretService/ListSecretSetGrants": "read",
99
+ "/toshi.workspace.v1.SecretService/UpdateSecretSet": "none",
63
100
  "/toshi.workspace.v1.ProjectService/ListProjects": "read",
101
+ "/toshi.workspace.v1.ProjectService/UpdateProject": "none",
64
102
  "/toshi.workspace.v1.ProjectService/ListRepositories": "read",
65
103
  "/toshi.workspace.v1.ProjectService/GetProject": "read",
66
104
  "/toshi.workspace.v1.ProjectService/ListBranchRules": "read",
67
- "/toshi.workspace.v1.ProjectService/CreateProject": "none",
105
+ "/toshi.workspace.v1.ProjectService/CreateProject": "keyed",
68
106
  "/toshi.workspace.v1.ProjectService/ClaimRepository": "none",
69
- "/toshi.workspace.v1.ProjectService/CreateEnvironment": "none",
107
+ "/toshi.workspace.v1.ProjectService/CreateEnvironment": "keyed",
70
108
  "/toshi.workspace.v1.ProjectService/DeleteEnvironment": "none",
71
109
  "/toshi.workspace.v1.ProjectService/SetBranchRules": "none",
72
110
  "/toshi.workspace.v1.ProjectService/CreateEmbedToken": "none",
73
111
  "/toshi.workspace.v1.ProjectService/ListEmbedTokens": "read",
74
112
  "/toshi.workspace.v1.ProjectService/RevokeEmbedToken": "none",
113
+ "/toshi.workspace.v1.ProjectService/GetProjectNetwork": "read",
114
+ "/toshi.workspace.v1.ProjectService/SetProjectNetwork": "none",
115
+ "/toshi.workspace.v1.ProjectService/GetEnvironmentNetwork": "read",
116
+ "/toshi.workspace.v1.ProjectService/SetEnvironmentNetwork": "none",
117
+ "/toshi.workspace.v1.ProjectService/GetEnvironmentRetention": "read",
118
+ "/toshi.workspace.v1.ProjectService/SetEnvironmentRetention": "none",
119
+ "/toshi.workspace.v1.ProjectService/SetEnvironmentVolumes": "none",
120
+ "/toshi.workspace.v1.ProjectService/UpdateEnvironment": "none",
75
121
  "/toshi.workspace.v1.WebhookService/CreateWebhookEndpoint": "keyed",
76
122
  "/toshi.workspace.v1.WebhookService/GetWebhookEndpoint": "read",
77
123
  "/toshi.workspace.v1.WebhookService/ListWebhookEndpoints": "read",
@@ -80,6 +126,9 @@ const procedureRetryClass = {
80
126
  "/toshi.workspace.v1.WebhookService/RotateWebhookEndpointSecret": "keyed",
81
127
  "/toshi.workspace.v1.WebhookService/ListWebhookDeliveries": "read",
82
128
  "/toshi.workspace.v1.WebhookService/ReplayWebhookEvents": "keyed",
129
+ // A retry returns the session the first call opened, with replayed set and
130
+ // no token, and opens nothing, so it cannot give the caller a session it can
131
+ // use.
83
132
  "/toshi.workspace.v1.DisplayService/OpenDisplaySession": "none",
84
133
  "/toshi.workspace.v1.DisplayService/GetDisplaySession": "read",
85
134
  "/toshi.workspace.v1.DisplayService/ListDisplaySessions": "read",
@@ -88,7 +137,7 @@ const procedureRetryClass = {
88
137
  "/toshi.workspace.v1.DisplayService/SendDisplayPointer": "none",
89
138
  "/toshi.workspace.v1.DisplayService/SetDisplayClipboard": "none",
90
139
  "/toshi.workspace.v1.DisplayService/TakeDisplayScreenshot": "none",
91
- "/toshi.workspace.v1.ChannelService/CreateChannel": "none",
140
+ "/toshi.workspace.v1.ChannelService/CreateChannel": "keyed",
92
141
  "/toshi.workspace.v1.ChannelService/ListChannels": "read",
93
142
  "/toshi.workspace.v1.ChannelService/GetChannel": "read",
94
143
  "/toshi.workspace.v1.ChannelService/GetChannelPolicy": "read",
@@ -101,28 +150,33 @@ const procedureRetryClass = {
101
150
  "/toshi.workspace.v1.ChannelService/DeleteChannel": "none",
102
151
  "/toshi.workspace.v1.ChannelService/ListForwards": "read",
103
152
  "/toshi.workspace.v1.ChannelService/GetLink": "read",
153
+ "/toshi.workspace.v1.ChannelService/ReplayChannelMessage": "none",
154
+ "/toshi.workspace.v1.ChannelService/SendChannelMessage": "keyed",
155
+ "/toshi.workspace.v1.ChannelService/TailChannelMessages": "none",
104
156
  "/toshi.workspace.v1.RecordingService/ListRecordings": "read",
105
157
  "/toshi.workspace.v1.RecordingService/GetRecording": "read",
106
158
  "/toshi.workspace.v1.RecordingService/CreateRecordingShareLink": "none",
107
159
  "/toshi.workspace.v1.RecordingService/ListRecordingShareLinks": "read",
108
160
  "/toshi.workspace.v1.RecordingService/RevokeRecordingShareLink": "none",
109
161
  "/toshi.workspace.v1.VolumeService/ListVolumes": "read",
162
+ "/toshi.workspace.v1.VolumeService/GetVolume": "read",
110
163
  "/toshi.workspace.v1.VolumeService/AttachVolume": "none",
111
164
  "/toshi.workspace.v1.VolumeService/DetachVolume": "none",
165
+ "/toshi.workspace.v1.VolumeService/ListVolumeVersions": "read",
166
+ "/toshi.workspace.v1.VolumeService/RestoreVolume": "none",
167
+ "/toshi.workspace.v1.VolumeService/CreateVolume": "keyed",
168
+ "/toshi.workspace.v1.VolumeService/UpdateVolume": "none",
169
+ "/toshi.workspace.v1.VolumeService/DeleteVolume": "none",
112
170
  "/toshi.workspace.v1.ArtifactService/ListArtifacts": "read",
113
171
  "/toshi.workspace.v1.ArtifactService/GetArtifact": "read",
114
172
  "/toshi.workspace.v1.ArtifactService/GetArtifactDownloadURL": "read",
115
173
  "/toshi.workspace.v1.ArtifactUploadService/ReserveUpload": "none",
116
174
  "/toshi.workspace.v1.ArtifactUploadService/CompleteUpload": "none",
117
175
  "/toshi.workspace.v1.WorkspaceService/GetWorkspace": "read",
176
+ "/toshi.workspace.v1.WorkspaceService/UpdateWorkspace": "none",
118
177
  "/toshi.workspace.v1.WorkspaceService/ListWorkspaces": "read",
119
178
  "/toshi.workspace.v1.WorkspaceService/GetWorkspaceBilling": "read",
120
179
  "/toshi.workspace.v1.WorkspaceService/GetWorkspaceStatement": "read",
121
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceOwnHostsOnly": "none",
122
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceAllowOverflow": "none",
123
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceAllowCacheWarm": "none",
124
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceCancelRunOnVmDeath": "none",
125
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceSpendLimits": "none",
126
180
  "/toshi.workspace.v1.WorkspaceService/ListEgressIPs": "read",
127
181
  "/toshi.workspace.v1.WorkspaceService/GetUsage": "read",
128
182
  "/toshi.workspace.v1.WorkspaceService/GetQueueTimes": "read",
@@ -131,38 +185,99 @@ const procedureRetryClass = {
131
185
  "/toshi.workspace.v1.WorkspaceService/CreateCheckoutSession": "none",
132
186
  "/toshi.workspace.v1.WorkspaceService/CreateBillingPortalSession": "none",
133
187
  "/toshi.workspace.v1.WorkspaceService/GetVersion": "read",
188
+ "/toshi.workspace.v1.WorkspaceService/ListSeats": "read",
189
+ "/toshi.workspace.v1.WorkspaceService/AssignSeat": "none",
190
+ "/toshi.workspace.v1.WorkspaceService/RemoveSeat": "none",
191
+ "/toshi.workspace.v1.WorkspaceService/DeleteWorkspace": "none",
192
+ "/toshi.workspace.v1.WorkspaceService/CancelWorkspaceDeletion": "none",
134
193
  "/toshi.workspace.v1.HostJobService/ListHostJobs": "read",
135
194
  "/toshi.workspace.v1.HostJobService/GetHostJob": "read",
136
195
  "/toshi.workspace.v1.HostJobService/CancelHostJob": "none",
137
196
  "/toshi.workspace.v1.HostJobService/GetHostJobFailure": "read",
138
197
  "/toshi.workspace.v1.HostJobService/GetHostJobTelemetry": "read",
139
- "/toshi.workspace.v1.HostJobService/GetHostJobDebugAccess": "read",
198
+ // Not a read: it hands out a debug key and a suspended workspace is refused
199
+ // it; the request carries no request_key, so it is never retried.
200
+ "/toshi.workspace.v1.HostJobService/GetHostJobDebugAccess": "none",
140
201
  "/toshi.workspace.v1.HostJobService/StopHostJobDebug": "none",
141
202
  "/toshi.workspace.v1.HostService/ListHosts": "read",
142
203
  "/toshi.workspace.v1.HostService/ListHostStorage": "read",
143
204
  "/toshi.workspace.v1.HostService/ListHostAlerts": "read",
144
205
  "/toshi.workspace.v1.HostService/SetHostState": "none",
145
206
  "/toshi.workspace.v1.HostService/RetireHost": "none",
146
- "/toshi.workspace.v1.HostService/CreateCloudHost": "none",
207
+ "/toshi.workspace.v1.HostService/CreateCloudHost": "keyed",
147
208
  "/toshi.workspace.v1.HostService/CheckCloudHost": "none",
148
209
  "/toshi.workspace.v1.HostService/CreateEnrollmentToken": "none",
149
210
  "/toshi.workspace.v1.TokenService/ListTokens": "read",
150
211
  "/toshi.workspace.v1.TokenService/CreateWorkspaceToken": "none",
212
+ "/toshi.workspace.v1.TokenService/RollWorkspaceToken": "none",
213
+ "/toshi.workspace.v1.TokenService/UpdateToken": "none",
151
214
  "/toshi.workspace.v1.TokenService/RevokeToken": "none",
152
215
  "/toshi.workspace.v1.AuditEventService/ListAuditEvents": "read",
216
+ "/toshi.workspace.v1.AuditEventService/ExportAuditEvents": "none",
153
217
  "/toshi.workspace.v1.FillerService/ListFillers": "read",
154
218
  "/toshi.workspace.v1.FillerService/PutFiller": "none",
155
219
  "/toshi.workspace.v1.FillerService/DeleteFiller": "none",
156
220
  "/toshi.workspace.v1.FillerService/SetFillerEnabled": "none",
157
221
  "/toshi.workspace.v1.FillerService/ListFillerRuns": "read",
222
+ "/toshi.workspace.v1.GrantService/CreateGrant": "keyed",
223
+ "/toshi.workspace.v1.GrantService/GetGrant": "read",
224
+ "/toshi.workspace.v1.GrantService/ListGrants": "read",
225
+ "/toshi.workspace.v1.GrantService/AcceptGrant": "none",
226
+ "/toshi.workspace.v1.GrantService/DeclineGrant": "none",
227
+ "/toshi.workspace.v1.GrantService/UpdateGrant": "none",
228
+ "/toshi.workspace.v1.GrantService/RevokeGrant": "none",
229
+ "/toshi.workspace.v1.GrantService/LeaveGrant": "none",
158
230
  "/toshi.workspace.v1.StorageProviderService/ListStorageProviders": "read",
159
- "/toshi.workspace.v1.StorageProviderService/CreateStorageProvider": "none",
231
+ "/toshi.workspace.v1.StorageProviderService/CreateStorageProvider": "keyed",
160
232
  "/toshi.workspace.v1.StorageProviderService/CheckStorageProvider": "none",
161
233
  "/toshi.workspace.v1.StorageProviderService/DeleteStorageProvider": "none",
162
234
  "/toshi.workspace.v1.ProductService/ListProducts": "read",
163
235
  "/toshi.workspace.v1.ProductService/UpdateProduct": "none",
164
- "/toshi.workspace.v1.ProductService/RemoveProduct": "none",
165
236
  "/toshi.workspace.v1.DiscoveryService/Discover": "read",
237
+ "/toshi.workspace.v1.ConnectionService/ListConnections": "read",
238
+ "/toshi.workspace.v1.ConnectionService/GetConnection": "read",
239
+ "/toshi.workspace.v1.ConnectionService/CreateConnection": "keyed",
240
+ "/toshi.workspace.v1.ConnectionService/DeleteConnection": "keyed",
241
+ "/toshi.workspace.v1.ConnectionService/RotateConnectionSecret": "keyed",
242
+ "/toshi.workspace.v1.ConnectionService/UpdateConnection": "none",
243
+ "/toshi.workspace.v1.ConnectionService/ListConnectionDeliveries": "read",
244
+ "/toshi.workspace.v1.ConnectionService/GetConnectionDelivery": "read",
245
+ "/toshi.workspace.v1.ConnectionService/ReplayConnectionDelivery": "keyed",
246
+ "/toshi.workspace.v1.ImageService/BuildImage": "none",
247
+ "/toshi.workspace.v1.ImageService/CreateImage": "keyed",
248
+ "/toshi.workspace.v1.ImageService/DeleteImage": "none",
249
+ "/toshi.workspace.v1.ImageService/GetImage": "read",
250
+ "/toshi.workspace.v1.ImageService/ListImages": "read",
251
+ "/toshi.workspace.v1.ImageService/UpdateImage": "none",
252
+ "/toshi.workspace.v1.RoleService/ListRoles": "read",
253
+ "/toshi.workspace.v1.RoleService/CreateRole": "keyed",
254
+ "/toshi.workspace.v1.RoleService/UpdateRole": "none",
255
+ "/toshi.workspace.v1.RoleService/DeleteRole": "none",
256
+ "/toshi.workspace.v1.RoleService/ListRoleGrants": "read",
257
+ "/toshi.workspace.v1.RoleService/GrantRole": "none",
258
+ "/toshi.workspace.v1.RoleService/RevokeRole": "none",
259
+ "/toshi.workspace.v1.RoleService/ListMembers": "read",
260
+ "/toshi.workspace.v1.RoleService/RemoveMember": "none",
261
+ "/toshi.workspace.v1.RoleService/CreateInvitation": "none",
262
+ "/toshi.workspace.v1.RoleService/ListInvitations": "read",
263
+ "/toshi.workspace.v1.RoleService/RevokeInvitation": "none",
264
+ // A repeat answers ALREADY_EXISTS ("a caller who is already a member fails
265
+ // with ALREADY_EXISTS"), so a retry would report failure for a call that
266
+ // worked; its permission's level is read although it changes state.
267
+ "/toshi.workspace.v1.RoleService/AcceptInvitation": "none",
268
+ "/toshi.workspace.v1.RoleService/ApproveMember": "none",
269
+ "/toshi.workspace.v1.RoleService/DeclineMember": "none",
270
+ "/toshi.workspace.v1.TriggerService/CreateTrigger": "keyed",
271
+ "/toshi.workspace.v1.TriggerService/GetTrigger": "read",
272
+ "/toshi.workspace.v1.TriggerService/ListTriggers": "read",
273
+ "/toshi.workspace.v1.TriggerService/UpdateTrigger": "keyed",
274
+ "/toshi.workspace.v1.TriggerService/DeleteTrigger": "keyed",
275
+ "/toshi.workspace.v1.DomainService/CreateDomain": "keyed",
276
+ "/toshi.workspace.v1.DomainService/GetDomain": "read",
277
+ "/toshi.workspace.v1.DomainService/ListDomains": "read",
278
+ "/toshi.workspace.v1.DomainService/VerifyDomain": "keyed",
279
+ "/toshi.workspace.v1.DomainService/IssueDomain": "keyed",
280
+ "/toshi.workspace.v1.DomainService/DeleteDomain": "none",
166
281
  };
167
282
  /** The retry class of a generated procedure path; an unknown path is "none". */
168
283
  export function retryClassFor(procedure) {
@@ -263,9 +378,96 @@ export function backoffDelay(policy, attempt) {
263
378
  if (limit <= 0) {
264
379
  return 0;
265
380
  }
266
- const random = globalThis.crypto.getRandomValues(new Uint32Array(1))[0] / 2 ** 32;
381
+ // The jitter is not a secret, so a non-cryptographic source is enough.
382
+ const random = Math.random();
267
383
  return Math.floor(random * (limit + 1));
268
384
  }
385
+ /**
386
+ * How long the server asked the client to wait before a retry, in
387
+ * milliseconds, or undefined when it named no wait. A RESOURCE_EXHAUSTED
388
+ * refusal names the wait in a google.rpc.RetryInfo detail and in its
389
+ * Retry-After header, the detail winning when both are there; the API's rate
390
+ * limit sends both, with the same whole seconds. Such a refusal is retried
391
+ * only when it names a wait: one without, such as a plan's limit, does not
392
+ * pass on a retry. An HTTP 429 without a Connect error body reaches the
393
+ * client as Code.Unavailable with the message "HTTP 429" and the response's
394
+ * headers (connect 2.2.0 protocol-connect/validate-response.js:33), and names
395
+ * the wait in its Retry-After. Every other error names none.
396
+ */
397
+ export function serverWaitMs(err) {
398
+ if (!(err instanceof ConnectError)) {
399
+ return undefined;
400
+ }
401
+ if (err.code === Code.ResourceExhausted) {
402
+ for (const detail of err.details) {
403
+ if (!("type" in detail) || detail.type !== "google.rpc.RetryInfo") {
404
+ continue;
405
+ }
406
+ const ms = decodeRetryInfoMs(detail.value);
407
+ if (ms !== undefined) {
408
+ return ms;
409
+ }
410
+ }
411
+ return parseRetryAfterMs(err.metadata.get("Retry-After"));
412
+ }
413
+ if (err.code === Code.Unavailable && err.rawMessage === "HTTP 429") {
414
+ return parseRetryAfterMs(err.metadata.get("Retry-After"));
415
+ }
416
+ return undefined;
417
+ }
418
+ /**
419
+ * The delay before retry attempt + 1: the backoff's, or the server's wait when
420
+ * that is longer, never more than maxBackoffMs.
421
+ */
422
+ export function retryDelayMs(policy, attempt, waitMs) {
423
+ return Math.min(Math.max(backoffDelay(policy, attempt), waitMs ?? 0), policy.maxBackoffMs);
424
+ }
425
+ /** A Retry-After of whole seconds, the form the API sends; a date or anything else names no wait. */
426
+ function parseRetryAfterMs(value) {
427
+ if (value === null || !/^\s*\d+\s*$/.test(value)) {
428
+ return undefined;
429
+ }
430
+ const ms = Number(value.trim()) * 1000;
431
+ return Number.isSafeInteger(ms) ? ms : undefined;
432
+ }
433
+ /**
434
+ * Reads google.rpc.RetryInfo's retry_delay (1), a google.protobuf.Duration of
435
+ * seconds (1) and nanos (2), as milliseconds, reading the wire format itself
436
+ * as errors.ts does for ErrorInfo.
437
+ */
438
+ function decodeRetryInfoMs(bytes) {
439
+ try {
440
+ const reader = new BinaryReader(bytes);
441
+ while (reader.pos < reader.len) {
442
+ const [field, wireType] = reader.tag();
443
+ if (field !== 1 || wireType !== WireType.LengthDelimited) {
444
+ reader.skip(wireType);
445
+ continue;
446
+ }
447
+ const duration = new BinaryReader(reader.bytes());
448
+ let seconds = 0;
449
+ let nanos = 0;
450
+ while (duration.pos < duration.len) {
451
+ const [part, partType] = duration.tag();
452
+ if (part === 1 && partType === WireType.Varint) {
453
+ seconds = Number(duration.int64());
454
+ }
455
+ else if (part === 2 && partType === WireType.Varint) {
456
+ nanos = duration.int32();
457
+ }
458
+ else {
459
+ duration.skip(partType);
460
+ }
461
+ }
462
+ const ms = seconds * 1000 + Math.ceil(nanos / 1e6);
463
+ return seconds >= 0 && nanos >= 0 && Number.isSafeInteger(ms) ? ms : undefined;
464
+ }
465
+ }
466
+ catch {
467
+ return undefined;
468
+ }
469
+ return undefined;
470
+ }
269
471
  /** Sleeps, rejecting at once when the signal aborts (retry.go:186-200). */
270
472
  export function sleep(ms, signal) {
271
473
  return new Promise((resolve, reject) => {