@miosa/sdk 1.2.4 → 1.2.6

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 (100) hide show
  1. package/README.md +75 -3
  2. package/dist/index.d.ts +323 -175
  3. package/dist/index.js +441 -386
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -2
  6. package/src/client.ts +0 -293
  7. package/src/errors.ts +0 -136
  8. package/src/http.test.ts +0 -374
  9. package/src/http.ts +0 -390
  10. package/src/index.ts +0 -564
  11. package/src/resources/admin.ts +0 -359
  12. package/src/resources/analytics.ts +0 -60
  13. package/src/resources/api-keys.ts +0 -135
  14. package/src/resources/audit-log.ts +0 -64
  15. package/src/resources/benchmarks.ts +0 -104
  16. package/src/resources/builder-sessions.ts +0 -75
  17. package/src/resources/channels.ts +0 -143
  18. package/src/resources/checkpoints.ts +0 -225
  19. package/src/resources/command-center.ts +0 -73
  20. package/src/resources/community.ts +0 -103
  21. package/src/resources/completions.ts +0 -104
  22. package/src/resources/computer-auto-stop.ts +0 -43
  23. package/src/resources/computer-env.ts +0 -76
  24. package/src/resources/computer-logs.ts +0 -50
  25. package/src/resources/computer-osa.ts +0 -76
  26. package/src/resources/computer-ports.ts +0 -91
  27. package/src/resources/computer-terminal.ts +0 -61
  28. package/src/resources/computer-volumes.ts +0 -64
  29. package/src/resources/computer.ts +0 -551
  30. package/src/resources/computers.ts +0 -75
  31. package/src/resources/credits.ts +0 -43
  32. package/src/resources/cron-jobs.ts +0 -191
  33. package/src/resources/custom_domains.ts +0 -123
  34. package/src/resources/dashboard.ts +0 -49
  35. package/src/resources/databases.ts +0 -218
  36. package/src/resources/deployments.test.ts +0 -194
  37. package/src/resources/deployments.ts +0 -1060
  38. package/src/resources/desktop.ts +0 -134
  39. package/src/resources/docker-deploy.test.ts +0 -102
  40. package/src/resources/docker-deploy.ts +0 -183
  41. package/src/resources/egress.test.ts +0 -318
  42. package/src/resources/egressAudit.ts +0 -245
  43. package/src/resources/egressNetwork.ts +0 -450
  44. package/src/resources/egressSecrets.ts +0 -577
  45. package/src/resources/email.ts +0 -212
  46. package/src/resources/embeddings.ts +0 -36
  47. package/src/resources/events.ts +0 -296
  48. package/src/resources/exec.ts +0 -319
  49. package/src/resources/external-keys.ts +0 -79
  50. package/src/resources/files.test.ts +0 -339
  51. package/src/resources/files.ts +0 -220
  52. package/src/resources/flat-custom-domains.ts +0 -127
  53. package/src/resources/functions.ts +0 -178
  54. package/src/resources/governance.test.ts +0 -355
  55. package/src/resources/governance.ts +0 -528
  56. package/src/resources/health-checks.ts +0 -165
  57. package/src/resources/integrations.ts +0 -183
  58. package/src/resources/mcp.ts +0 -70
  59. package/src/resources/models.ts +0 -45
  60. package/src/resources/network_policy.ts +0 -73
  61. package/src/resources/open-computers/agents.ts +0 -91
  62. package/src/resources/open-computers/apps.ts +0 -102
  63. package/src/resources/open-computers/clusters.ts +0 -88
  64. package/src/resources/open-computers/desktop.ts +0 -34
  65. package/src/resources/open-computers/files.ts +0 -97
  66. package/src/resources/open-computers/hosts.ts +0 -85
  67. package/src/resources/open-computers/index.ts +0 -98
  68. package/src/resources/open-computers/jobs.ts +0 -75
  69. package/src/resources/open-computers/open_computers.test.ts +0 -288
  70. package/src/resources/open-computers/secrets.ts +0 -115
  71. package/src/resources/open-computers/terminal.ts +0 -33
  72. package/src/resources/open-computers/tunnels.ts +0 -87
  73. package/src/resources/open-computers/types.ts +0 -343
  74. package/src/resources/open-computers/workspaces.ts +0 -135
  75. package/src/resources/org-invites.ts +0 -189
  76. package/src/resources/phase1.test.ts +0 -187
  77. package/src/resources/project-auth.ts +0 -142
  78. package/src/resources/project-integrations.ts +0 -133
  79. package/src/resources/provider-defaults.ts +0 -89
  80. package/src/resources/quotas.ts +0 -77
  81. package/src/resources/regions.ts +0 -94
  82. package/src/resources/sandbox-processes.ts +0 -112
  83. package/src/resources/sandbox-shares.ts +0 -83
  84. package/src/resources/sandbox-templates.ts +0 -195
  85. package/src/resources/sandboxes.live.test.ts +0 -92
  86. package/src/resources/sandboxes.test.ts +0 -653
  87. package/src/resources/sandboxes.ts +0 -1433
  88. package/src/resources/settings.ts +0 -143
  89. package/src/resources/snapshots-standalone.ts +0 -51
  90. package/src/resources/storage.ts +0 -221
  91. package/src/resources/tenant-events.ts +0 -32
  92. package/src/resources/tenant.ts +0 -148
  93. package/src/resources/usage.ts +0 -85
  94. package/src/resources/volumes.ts +0 -117
  95. package/src/resources/webhooks.ts +0 -224
  96. package/src/resources/workspace-invites.ts +0 -188
  97. package/src/resources/workspace-members.test.ts +0 -121
  98. package/src/resources/workspace-members.ts +0 -143
  99. package/src/resources/workspaces.ts +0 -285
  100. package/src/types.ts +0 -463
@@ -1,318 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import type { HttpClient } from "../http.js";
3
- import { EgressAudit, SandboxAudit } from "./egressAudit.js";
4
- import { EgressNetwork, SandboxNetwork } from "./egressNetwork.js";
5
- import { EgressSecrets, OAuthFlow, SandboxSecrets } from "./egressSecrets.js";
6
-
7
- const mockGet = vi.fn();
8
- const mockPost = vi.fn();
9
- const mockPatch = vi.fn();
10
- const mockDelete = vi.fn();
11
- const mockRequest = vi.fn();
12
- const mockStream = vi.fn();
13
- const mockGetBinary = vi.fn();
14
-
15
- function makeHttp(): HttpClient {
16
- const http = {} as HttpClient;
17
- http.get = mockGet;
18
- http.post = mockPost;
19
- http.patch = mockPatch;
20
- http.delete = mockDelete;
21
- http.request = mockRequest;
22
- http.stream = mockStream;
23
- http.getBinary = mockGetBinary;
24
- return http;
25
- }
26
-
27
- beforeEach(() => {
28
- vi.clearAllMocks();
29
- });
30
-
31
- // ─── client.secrets ──────────────────────────────────────────────────────────
32
-
33
- describe("EgressSecrets", () => {
34
- it("set() POSTs to /egress/secrets with the right body", async () => {
35
- mockPost.mockResolvedValue({
36
- data: { id: "sec_1", name: "OPENAI_API_KEY" },
37
- });
38
-
39
- const client = new EgressSecrets(makeHttp());
40
- const secret = await client.set({
41
- name: "OPENAI_API_KEY",
42
- value: "sk-abc123",
43
- type: "api_key",
44
- scope: "user",
45
- exposeAsEnv: "OPENAI_API_KEY",
46
- });
47
-
48
- expect(mockPost).toHaveBeenCalledWith("/egress/secrets", {
49
- name: "OPENAI_API_KEY",
50
- value: "sk-abc123",
51
- type: "api_key",
52
- scope: "user",
53
- expose_as_env: "OPENAI_API_KEY",
54
- });
55
- expect(secret.id).toBe("sec_1");
56
- });
57
-
58
- it("list({ scope: 'user' }) GETs /egress/secrets?scope=user", async () => {
59
- mockGet.mockResolvedValue({ data: [{ id: "sec_1" }, { id: "sec_2" }] });
60
-
61
- const result = await new EgressSecrets(makeHttp()).list({ scope: "user" });
62
-
63
- expect(mockGet).toHaveBeenCalledWith("/egress/secrets", { scope: "user" });
64
- expect(result).toHaveLength(2);
65
- });
66
-
67
- it("get() calls /egress/secrets/:id", async () => {
68
- mockGet.mockResolvedValue({ data: { id: "sec_1" } });
69
- await new EgressSecrets(makeHttp()).get("sec_1");
70
- expect(mockGet).toHaveBeenCalledWith("/egress/secrets/sec_1");
71
- });
72
-
73
- it("rotate() PATCHes /egress/secrets/:id with new value", async () => {
74
- mockPatch.mockResolvedValue({ data: { id: "sec_1" } });
75
- await new EgressSecrets(makeHttp()).rotate("sec_1", {
76
- newValue: "sk-new",
77
- expiresAt: "2027-01-01T00:00:00Z",
78
- });
79
- expect(mockPatch).toHaveBeenCalledWith("/egress/secrets/sec_1", {
80
- value: "sk-new",
81
- expires_at: "2027-01-01T00:00:00Z",
82
- });
83
- });
84
-
85
- it("delete() DELETEs /egress/secrets/:id", async () => {
86
- mockDelete.mockResolvedValue(undefined);
87
- await new EgressSecrets(makeHttp()).delete("sec_1");
88
- expect(mockDelete).toHaveBeenCalledWith("/egress/secrets/sec_1");
89
- });
90
-
91
- it("connect() returns an OAuthFlow with authorizeUrl + state", async () => {
92
- mockPost.mockResolvedValue({
93
- data: {
94
- authorize_url: "https://github.com/login/oauth/authorize?state=xyz",
95
- state: "xyz",
96
- },
97
- });
98
-
99
- const flow = await new EgressSecrets(makeHttp()).connect({
100
- provider: "github",
101
- exposeAsEnv: "GITHUB_TOKEN",
102
- });
103
-
104
- expect(mockPost).toHaveBeenCalledWith("/egress/oauth/start", {
105
- provider: "github",
106
- expose_as_env: "GITHUB_TOKEN",
107
- });
108
- expect(flow).toBeInstanceOf(OAuthFlow);
109
- expect(flow.authorizeUrl).toBe(
110
- "https://github.com/login/oauth/authorize?state=xyz",
111
- );
112
- expect(flow.state).toBe("xyz");
113
- });
114
-
115
- it("OAuthFlow.waitForCompletion polls /egress/oauth/status", async () => {
116
- mockPost.mockResolvedValue({
117
- data: { authorize_url: "https://example.com", state: "xyz" },
118
- });
119
- mockGet.mockResolvedValueOnce({
120
- data: { status: "completed", secret_id: "sec_new" },
121
- });
122
-
123
- const flow = await new EgressSecrets(makeHttp()).connect({
124
- provider: "github",
125
- });
126
- const result = await flow.waitForCompletion({
127
- timeoutSec: 2,
128
- pollIntervalMs: 1,
129
- });
130
-
131
- expect(mockGet).toHaveBeenCalledWith("/egress/oauth/status", {
132
- state: "xyz",
133
- });
134
- expect(result.status).toBe("completed");
135
- });
136
-
137
- it("createBinding() POSTs to /egress/bindings", async () => {
138
- mockPost.mockResolvedValue({ data: { id: "bnd_1" } });
139
- await new EgressSecrets(makeHttp()).createBinding({
140
- secretId: "sec_1",
141
- resourceId: "sbx_1",
142
- resourceType: "sandbox",
143
- exposeAsEnv: "OPENAI_API_KEY",
144
- });
145
- expect(mockPost).toHaveBeenCalledWith("/egress/bindings", {
146
- secret_id: "sec_1",
147
- resource_id: "sbx_1",
148
- resource_type: "sandbox",
149
- expose_as_env: "OPENAI_API_KEY",
150
- });
151
- });
152
-
153
- it("providers() GETs /egress/oauth/providers", async () => {
154
- mockGet.mockResolvedValue({
155
- data: [{ name: "github" }, { name: "slack" }],
156
- });
157
- const out = await new EgressSecrets(makeHttp()).providers();
158
- expect(mockGet).toHaveBeenCalledWith("/egress/oauth/providers");
159
- expect(out).toHaveLength(2);
160
- });
161
- });
162
-
163
- // ─── client.network ──────────────────────────────────────────────────────────
164
-
165
- describe("EgressNetwork", () => {
166
- it("allow() POSTs to /egress/allowlist with effect=allow", async () => {
167
- mockPost.mockResolvedValue({ data: { id: "rul_1" } });
168
- await new EgressNetwork(makeHttp()).allow("api.openai.com", {
169
- methods: ["GET", "POST"],
170
- pathGlob: "/v1/*",
171
- });
172
- expect(mockPost).toHaveBeenCalledWith("/egress/allowlist", {
173
- host: "api.openai.com",
174
- effect: "allow",
175
- methods: ["GET", "POST"],
176
- path_glob: "/v1/*",
177
- });
178
- });
179
-
180
- it("deny() POSTs with effect=deny", async () => {
181
- mockPost.mockResolvedValue({ data: { id: "rul_2" } });
182
- await new EgressNetwork(makeHttp()).deny("169.254.169.254");
183
- expect(mockPost).toHaveBeenCalledWith("/egress/allowlist", {
184
- host: "169.254.169.254",
185
- effect: "deny",
186
- });
187
- });
188
-
189
- it("lockdown() PATCHes /egress/policies with mode=enforce", async () => {
190
- mockPatch.mockResolvedValue({ data: {} });
191
- await new EgressNetwork(makeHttp()).lockdown();
192
- expect(mockPatch).toHaveBeenCalledWith("/egress/policies", {
193
- mode: "enforce",
194
- });
195
- });
196
-
197
- it("observe() PATCHes /egress/policies with mode=audit_only", async () => {
198
- mockPatch.mockResolvedValue({ data: {} });
199
- await new EgressNetwork(makeHttp()).observe();
200
- expect(mockPatch).toHaveBeenCalledWith("/egress/policies", {
201
- mode: "audit_only",
202
- });
203
- });
204
-
205
- it("suggestions() GETs /egress/audit/suggestions with resource_id", async () => {
206
- mockGet.mockResolvedValue({ data: [] });
207
- await new EgressNetwork(makeHttp()).suggestions({
208
- resourceId: "sbx_1",
209
- since: "1d",
210
- });
211
- expect(mockGet).toHaveBeenCalledWith("/egress/audit/suggestions", {
212
- resource_id: "sbx_1",
213
- since: "1d",
214
- });
215
- });
216
-
217
- it("policies() GETs /egress/policies", async () => {
218
- mockGet.mockResolvedValue({ data: [{ id: "pol_1" }] });
219
- const out = await new EgressNetwork(makeHttp()).policies();
220
- expect(mockGet).toHaveBeenCalledWith("/egress/policies", {});
221
- expect(out).toHaveLength(1);
222
- });
223
-
224
- it("removeRule() DELETEs /egress/allowlist/:id", async () => {
225
- mockDelete.mockResolvedValue(undefined);
226
- await new EgressNetwork(makeHttp()).removeRule("rul_1");
227
- expect(mockDelete).toHaveBeenCalledWith("/egress/allowlist/rul_1");
228
- });
229
- });
230
-
231
- // ─── client.audit ────────────────────────────────────────────────────────────
232
-
233
- describe("EgressAudit", () => {
234
- it("list() GETs /egress/audit", async () => {
235
- mockGet.mockResolvedValue({
236
- data: [
237
- { id: "evt_1", host: "api.openai.com" },
238
- { id: "evt_2", host: "github.com" },
239
- ],
240
- });
241
- const out = await new EgressAudit(makeHttp()).list();
242
- expect(mockGet).toHaveBeenCalledWith("/egress/audit", {});
243
- expect(out).toHaveLength(2);
244
- });
245
-
246
- it("list() passes filters as query params", async () => {
247
- mockGet.mockResolvedValue({ data: [] });
248
- await new EgressAudit(makeHttp()).list({
249
- resourceId: "sbx_1",
250
- host: "api.openai.com",
251
- action: "denied",
252
- limit: 50,
253
- });
254
- expect(mockGet).toHaveBeenCalledWith("/egress/audit", {
255
- resource_id: "sbx_1",
256
- host: "api.openai.com",
257
- action: "denied",
258
- limit: 50,
259
- });
260
- });
261
-
262
- it("get() returns a single event", async () => {
263
- mockGet.mockResolvedValue({ data: { id: "evt_1" } });
264
- const out = await new EgressAudit(makeHttp()).get("evt_1");
265
- expect(mockGet).toHaveBeenCalledWith("/egress/audit/evt_1");
266
- expect(out.id).toBe("evt_1");
267
- });
268
- });
269
-
270
- // ─── sandbox.* scoped namespaces ─────────────────────────────────────────────
271
-
272
- describe("Sandbox-scoped egress", () => {
273
- it("sandbox.secrets.set() injects resource_id + resource_type", async () => {
274
- mockPost.mockResolvedValue({ data: { id: "sec_1" } });
275
- await new SandboxSecrets(makeHttp(), "sbx_abc").set({
276
- name: "OPENAI_API_KEY",
277
- value: "sk-abc",
278
- });
279
- expect(mockPost).toHaveBeenCalledWith("/egress/secrets", {
280
- name: "OPENAI_API_KEY",
281
- value: "sk-abc",
282
- type: "api_key",
283
- scope: "user",
284
- resource_id: "sbx_abc",
285
- resource_type: "sandbox",
286
- });
287
- });
288
-
289
- it("sandbox.audit.list() includes resource_id in query params", async () => {
290
- mockGet.mockResolvedValue({ data: [] });
291
- await new SandboxAudit(makeHttp(), "sbx_abc").list();
292
- expect(mockGet).toHaveBeenCalledWith("/egress/audit", {
293
- resource_id: "sbx_abc",
294
- resource_type: "sandbox",
295
- });
296
- });
297
-
298
- it("sandbox.network.allow() scopes the rule to the sandbox", async () => {
299
- mockPost.mockResolvedValue({ data: { id: "rul_1" } });
300
- await new SandboxNetwork(makeHttp(), "sbx_abc").allow("api.openai.com");
301
- expect(mockPost).toHaveBeenCalledWith("/egress/allowlist", {
302
- host: "api.openai.com",
303
- effect: "allow",
304
- resource_id: "sbx_abc",
305
- resource_type: "sandbox",
306
- });
307
- });
308
-
309
- it("sandbox.network.lockdown() scopes the policy patch", async () => {
310
- mockPatch.mockResolvedValue({ data: {} });
311
- await new SandboxNetwork(makeHttp(), "sbx_abc").lockdown();
312
- expect(mockPatch).toHaveBeenCalledWith("/egress/policies", {
313
- mode: "enforce",
314
- resource_id: "sbx_abc",
315
- resource_type: "sandbox",
316
- });
317
- });
318
- });
@@ -1,245 +0,0 @@
1
- /**
2
- * Egress audit log — paginated query + live tail.
3
- *
4
- * Backed by:
5
- * GET /api/v1/egress/audit
6
- * GET /api/v1/egress/audit/:id
7
- *
8
- * `client.audit.tail()` long-polls the REST endpoint and yields new
9
- * events as they arrive. The sandbox-scoped variant
10
- * (`sandbox.audit.tail()`) upgrades to a live SSE connection backed by
11
- * `GET /sandboxes/:id/audit/stream` so the tail latency is
12
- * sub-second.
13
- */
14
-
15
- import type { HttpClient } from "../http.js";
16
-
17
- // ── Resource shapes ──────────────────────────────────────────────────────────
18
-
19
- export interface EgressAuditEvent {
20
- id: string;
21
- action?: string;
22
- effect?: string;
23
- host?: string;
24
- method?: string;
25
- path?: string;
26
- status_code?: number;
27
- actor_id?: string;
28
- resource_id?: string;
29
- resource_type?: string;
30
- policy_id?: string;
31
- rule_id?: string;
32
- external_user_id?: string;
33
- external_workspace_id?: string;
34
- metadata?: Record<string, unknown>;
35
- inserted_at?: string;
36
- timestamp?: string;
37
- [key: string]: unknown;
38
- }
39
-
40
- // ── Request payloads ─────────────────────────────────────────────────────────
41
-
42
- export interface AuditListParams {
43
- resourceId?: string;
44
- resource_id?: string;
45
- resourceType?: string;
46
- resource_type?: string;
47
- host?: string;
48
- action?: string;
49
- since?: string;
50
- until?: string;
51
- limit?: number;
52
- cursor?: string;
53
- externalUserId?: string;
54
- external_user_id?: string;
55
- externalWorkspaceId?: string;
56
- external_workspace_id?: string;
57
- }
58
-
59
- export interface AuditTailParams extends AuditListParams {
60
- pollIntervalMs?: number;
61
- }
62
-
63
- // ── Helpers ───────────────────────────────────────────────────────────────────
64
-
65
- interface WireEnvelope<T> {
66
- data?: T;
67
- event?: T;
68
- items?: T;
69
- }
70
-
71
- function unwrap<T>(payload: unknown): T {
72
- if (payload && typeof payload === "object") {
73
- const p = payload as Record<string, unknown>;
74
- for (const k of ["data", "event", "items"]) {
75
- if (k in p) return p[k] as T;
76
- }
77
- }
78
- return payload as T;
79
- }
80
-
81
- function unwrapList<T>(payload: unknown): T[] {
82
- if (Array.isArray(payload)) return payload as T[];
83
- if (payload && typeof payload === "object") {
84
- const p = payload as Record<string, unknown>;
85
- for (const k of ["data", "events", "audit", "items"]) {
86
- if (Array.isArray(p[k])) return p[k] as T[];
87
- }
88
- }
89
- return [];
90
- }
91
-
92
- function stripUndefined(
93
- input: Record<string, unknown>,
94
- ): Record<string, unknown> {
95
- return Object.fromEntries(
96
- Object.entries(input).filter(([, v]) => v !== undefined),
97
- );
98
- }
99
-
100
- function pickFirst<T>(...values: Array<T | undefined>): T | undefined {
101
- for (const v of values) if (v !== undefined) return v;
102
- return undefined;
103
- }
104
-
105
- function listQuery(
106
- params: AuditListParams,
107
- ): Record<string, string | number | boolean | undefined> {
108
- return stripUndefined({
109
- resource_id: pickFirst(params.resourceId, params.resource_id),
110
- resource_type: pickFirst(params.resourceType, params.resource_type),
111
- host: params.host,
112
- action: params.action,
113
- since: params.since,
114
- until: params.until,
115
- limit: params.limit,
116
- cursor: params.cursor,
117
- external_user_id: pickFirst(params.externalUserId, params.external_user_id),
118
- external_workspace_id: pickFirst(
119
- params.externalWorkspaceId,
120
- params.external_workspace_id,
121
- ),
122
- }) as Record<string, string | number | boolean | undefined>;
123
- }
124
-
125
- function sleep(ms: number): Promise<void> {
126
- return new Promise((resolve) => setTimeout(resolve, ms));
127
- }
128
-
129
- // ── Main resource ─────────────────────────────────────────────────────────────
130
-
131
- export class EgressAudit {
132
- constructor(protected readonly http: HttpClient) {}
133
-
134
- /** List audit events with optional filters. */
135
- async list(params: AuditListParams = {}): Promise<EgressAuditEvent[]> {
136
- const data = await this.http.get<unknown>(
137
- "/egress/audit",
138
- listQuery(params),
139
- );
140
- return unwrapList<EgressAuditEvent>(data);
141
- }
142
-
143
- /** Get a single audit event by id. */
144
- async get(id: string): Promise<EgressAuditEvent> {
145
- const data = await this.http.get<WireEnvelope<EgressAuditEvent>>(
146
- `/egress/audit/${id}`,
147
- );
148
- return unwrap<EgressAuditEvent>(data);
149
- }
150
-
151
- /**
152
- * Long-poll the audit endpoint and yield new events as they appear.
153
- *
154
- * Tenant-wide `client.audit.tail()` is REST-based long polling. A
155
- * live WebSocket / SSE tail is only available for the sandbox-scoped
156
- * variant — see {@link SandboxAudit.tail}.
157
- */
158
- async *tail(
159
- params: AuditTailParams = {},
160
- ): AsyncIterableIterator<EgressAuditEvent> {
161
- const pollMs = params.pollIntervalMs ?? 2000;
162
- let since: string | undefined = params.since;
163
- const seen = new Set<string>();
164
- while (true) {
165
- const queryParams: AuditListParams = { ...params };
166
- if (since !== undefined) queryParams.since = since;
167
- const data = await this.http.get<unknown>(
168
- "/egress/audit",
169
- listQuery(queryParams),
170
- );
171
- const events = unwrapList<EgressAuditEvent>(data);
172
- for (const event of events) {
173
- if (event.id && seen.has(event.id)) continue;
174
- if (event.id) seen.add(event.id);
175
- yield event;
176
- const ts = event.inserted_at ?? event.timestamp;
177
- if (typeof ts === "string") since = ts;
178
- }
179
- await sleep(pollMs);
180
- }
181
- }
182
- }
183
-
184
- // ── Resource-scoped wrappers ─────────────────────────────────────────────────
185
-
186
- /**
187
- * Sandbox-bound view of {@link EgressAudit}. `list()` pre-scopes
188
- * `resource_id` + `resource_type="sandbox"`. `tail()` upgrades to the
189
- * per-sandbox SSE stream for sub-second tail latency.
190
- */
191
- export class SandboxAudit {
192
- protected readonly resourceType: string = "sandbox";
193
- private readonly delegate: EgressAudit;
194
-
195
- constructor(
196
- protected readonly http: HttpClient,
197
- protected readonly resourceId: string,
198
- ) {
199
- this.delegate = new EgressAudit(http);
200
- }
201
-
202
- list(params: AuditListParams = {}): Promise<EgressAuditEvent[]> {
203
- return this.delegate.list({
204
- ...params,
205
- resource_id: params.resourceId ?? params.resource_id ?? this.resourceId,
206
- resource_type:
207
- params.resourceType ?? params.resource_type ?? this.resourceType,
208
- });
209
- }
210
-
211
- get(id: string): Promise<EgressAuditEvent> {
212
- return this.delegate.get(id);
213
- }
214
-
215
- /** SSE tail of the sandbox-scoped audit stream. */
216
- async *tail(
217
- params: AuditTailParams = {},
218
- ): AsyncIterableIterator<EgressAuditEvent> {
219
- const streamPath =
220
- this.resourceType === "sandbox"
221
- ? `/sandboxes/${this.resourceId}/audit/stream`
222
- : `/computers/${this.resourceId}/audit/stream`;
223
- try {
224
- const stream = this.http.stream<EgressAuditEvent>(streamPath, {
225
- method: "GET",
226
- });
227
- for await (const event of stream) {
228
- yield event;
229
- }
230
- } catch {
231
- // SSE endpoint unavailable — fall back to long-poll on the
232
- // tenant-wide endpoint with this resource pre-filtered.
233
- yield* this.delegate.tail({
234
- ...params,
235
- resource_id: this.resourceId,
236
- resource_type: this.resourceType,
237
- });
238
- }
239
- }
240
- }
241
-
242
- /** Computer-bound audit — same surface, `resource_type="computer"`. */
243
- export class ComputerAudit extends SandboxAudit {
244
- protected readonly resourceType: string = "computer";
245
- }