@meshagent/meshagent 0.35.5 → 0.35.7

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 (47) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/browser/containers-client.d.ts +79 -2
  3. package/dist/browser/containers-client.js +341 -19
  4. package/dist/browser/database-client.d.ts +95 -24
  5. package/dist/browser/database-client.js +150 -49
  6. package/dist/browser/messaging-client.d.ts +33 -52
  7. package/dist/browser/messaging-client.js +180 -184
  8. package/dist/browser/participant.d.ts +5 -3
  9. package/dist/browser/participant.js +9 -1
  10. package/dist/browser/room-client.js +2 -0
  11. package/dist/browser/room-event.d.ts +6 -2
  12. package/dist/browser/room-event.js +4 -2
  13. package/dist/browser/secrets-client.d.ts +86 -16
  14. package/dist/browser/secrets-client.js +243 -44
  15. package/dist/browser/storage-client.d.ts +17 -4
  16. package/dist/browser/storage-client.js +141 -30
  17. package/dist/esm/containers-client.d.ts +79 -2
  18. package/dist/esm/containers-client.js +341 -19
  19. package/dist/esm/database-client.d.ts +95 -24
  20. package/dist/esm/database-client.js +150 -49
  21. package/dist/esm/messaging-client.d.ts +33 -52
  22. package/dist/esm/messaging-client.js +179 -180
  23. package/dist/esm/participant.d.ts +5 -3
  24. package/dist/esm/participant.js +9 -1
  25. package/dist/esm/room-client.js +2 -0
  26. package/dist/esm/room-event.d.ts +6 -2
  27. package/dist/esm/room-event.js +4 -2
  28. package/dist/esm/secrets-client.d.ts +86 -16
  29. package/dist/esm/secrets-client.js +243 -44
  30. package/dist/esm/storage-client.d.ts +17 -4
  31. package/dist/esm/storage-client.js +140 -30
  32. package/dist/node/containers-client.d.ts +79 -2
  33. package/dist/node/containers-client.js +341 -19
  34. package/dist/node/database-client.d.ts +95 -24
  35. package/dist/node/database-client.js +150 -49
  36. package/dist/node/messaging-client.d.ts +33 -52
  37. package/dist/node/messaging-client.js +180 -184
  38. package/dist/node/participant.d.ts +5 -3
  39. package/dist/node/participant.js +9 -1
  40. package/dist/node/room-client.js +2 -0
  41. package/dist/node/room-event.d.ts +6 -2
  42. package/dist/node/room-event.js +4 -2
  43. package/dist/node/secrets-client.d.ts +86 -16
  44. package/dist/node/secrets-client.js +243 -44
  45. package/dist/node/storage-client.d.ts +17 -4
  46. package/dist/node/storage-client.js +141 -30
  47. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [0.35.7]
2
+ - Added container build lifecycle and image management in the TS SDK (start/build returning build IDs, list/cancel/delete builds, build logs, load/save/push/delete images) plus exec stderr streams and stricter status decoding.
3
+ - Breaking: container build APIs now return build IDs and `stop` defaults to non-forced.
4
+ - Added database namespace support and new operations (count, inspect, restore/checkout, listVersions with metadata) plus typed indexes and search offset.
5
+ - Added secrets client overhaul: OAuth/secret request handlers, offline OAuth tokens, request/provide/reject secret flows, and flexible get/set secret by id/type/name.
6
+ - Added storage enhancements: `stat`, upload MIME inference, storage entries include created/updated timestamps, and file updated/deleted events now include participant IDs.
7
+ - Breaking: messaging stream APIs removed; messaging now uses queued sends with start/stop, and RoomClient starts messaging automatically.
8
+
9
+ ## [0.35.6]
10
+ - New `@meshagent/meshagent-ts-auth` package provides framework-agnostic OAuth/PKCE login, token storage/refresh, and access-token providers.
11
+ - New `@meshagent/meshagent-react-dev` package adds developer console hooks for logs, terminal sessions, and webterm/ghostty integrations.
12
+ - Breaking: `@meshagent/meshagent-react-auth` now builds on `@meshagent/meshagent-ts-auth` and React Query; built-in auth primitives and the LoginScope component were removed in favor of hook-based APIs.
13
+ - TypeScript storage uploads now honor server-provided `chunk_size` pull headers for adaptive chunking.
14
+ - Async-iterable subscriptions in the React package now call iterator `return()` on unsubscribe to clean up resources.
15
+ - Dependency updates: `react`/`react-dom` ^19.1.8, `@tanstack/react-query`/`@tanstack/react-query-devtools` ^5.95.2, `ghostty-web` ^0.4.0, `wasm-webterm` (GitHub), `jest` ^30.3.0, `@types/jest` ^30.0.0, `ts-jest` ^29.4.6, `esbuild` ^0.25.0, `@types/react` ^19.1.8, `@types/react-dom` ^19.1.8.
16
+
1
17
  ## [0.35.5]
2
18
  - Stability
3
19
 
@@ -4,14 +4,25 @@ import { RoomClient } from "./room-client";
4
4
  export interface DockerSecret {
5
5
  username: string;
6
6
  password: string;
7
- registry: string;
7
+ registry?: string | null;
8
8
  email?: string;
9
9
  }
10
10
  export interface ContainerImage {
11
11
  id: string;
12
12
  tags: string[];
13
13
  size?: number;
14
- labels: Record<string, unknown>;
14
+ labels: Record<string, string>;
15
+ }
16
+ export interface ImportedImage {
17
+ resolvedRef: string;
18
+ refs: string[];
19
+ }
20
+ export type BuildJobStatus = "queued" | "running" | "failed" | "cancelled" | "succeeded";
21
+ export interface BuildJob {
22
+ id: string;
23
+ tag: string;
24
+ status: BuildJobStatus;
25
+ exitCode?: number;
15
26
  }
16
27
  export interface ContainerParticipantInfo {
17
28
  id: string;
@@ -31,16 +42,24 @@ export interface ContainerLogsSession {
31
42
  result: Promise<void>;
32
43
  cancel(): Promise<void>;
33
44
  }
45
+ export interface BuildLogsSession {
46
+ stream: AsyncIterable<string>;
47
+ result: Promise<number | null>;
48
+ cancel(): Promise<void>;
49
+ }
34
50
  export declare class ExecSession {
35
51
  readonly command: string;
36
52
  readonly result: Promise<number>;
37
53
  readonly previousOutput: Uint8Array[];
54
+ readonly previousError: Uint8Array[];
38
55
  readonly output: AsyncIterable<Uint8Array>;
56
+ readonly stderr: AsyncIterable<Uint8Array>;
39
57
  private readonly requestId;
40
58
  private readonly containerId;
41
59
  private readonly tty?;
42
60
  private readonly resultCompleter;
43
61
  private readonly outputController;
62
+ private readonly errorController;
44
63
  private readonly queuedInput;
45
64
  private inputClosed;
46
65
  private closed;
@@ -64,6 +83,7 @@ export declare class ExecSession {
64
83
  close(status: number): void;
65
84
  closeError(error: unknown): void;
66
85
  addOutput(data: Uint8Array): void;
86
+ addError(data: Uint8Array): void;
67
87
  get isClosed(): boolean;
68
88
  private queueInput;
69
89
  private closeInputStream;
@@ -76,10 +96,32 @@ export declare class ContainersClient {
76
96
  private unexpectedResponseError;
77
97
  private invoke;
78
98
  listImages(): Promise<ContainerImage[]>;
99
+ deleteImage(params: {
100
+ image: string;
101
+ }): Promise<void>;
79
102
  pullImage(params: {
80
103
  tag: string;
81
104
  credentials?: DockerSecret[];
82
105
  }): Promise<void>;
106
+ pushImage(params: {
107
+ tag: string;
108
+ credentials?: DockerSecret[];
109
+ private?: boolean;
110
+ }): Promise<string>;
111
+ load(params: {
112
+ archivePath: string;
113
+ }): Promise<ImportedImage>;
114
+ loadImage(params: {
115
+ mounts: ContainerMountSpec[];
116
+ archivePath: string;
117
+ private?: boolean;
118
+ }): Promise<string>;
119
+ saveImage(params: {
120
+ tag: string;
121
+ mounts: ContainerMountSpec[];
122
+ archivePath: string;
123
+ private?: boolean;
124
+ }): Promise<string>;
83
125
  run(params: {
84
126
  image: string;
85
127
  command?: string;
@@ -96,6 +138,37 @@ export declare class ContainersClient {
96
138
  writableRootFs?: boolean;
97
139
  private?: boolean;
98
140
  }): Promise<string>;
141
+ startBuild(params: {
142
+ tag: string;
143
+ mounts: ContainerMountSpec[];
144
+ contextPath: string;
145
+ dockerfilePath?: string;
146
+ private?: boolean;
147
+ credentials?: DockerSecret[];
148
+ contextArchivePath?: string;
149
+ contextArchiveRef?: string;
150
+ contextArchiveMountPath?: string;
151
+ contextArchiveArch?: string;
152
+ }): Promise<string>;
153
+ build(params: {
154
+ tag: string;
155
+ mounts: ContainerMountSpec[];
156
+ contextPath: string;
157
+ dockerfilePath?: string;
158
+ private?: boolean;
159
+ credentials?: DockerSecret[];
160
+ contextArchivePath?: string;
161
+ contextArchiveRef?: string;
162
+ contextArchiveMountPath?: string;
163
+ contextArchiveArch?: string;
164
+ }): Promise<string>;
165
+ listBuilds(): Promise<BuildJob[]>;
166
+ cancelBuild(params: {
167
+ buildId: string;
168
+ }): Promise<void>;
169
+ deleteBuild(params: {
170
+ buildId: string;
171
+ }): Promise<void>;
99
172
  runService(params: {
100
173
  serviceId: string;
101
174
  env?: Record<string, string>;
@@ -119,6 +192,10 @@ export declare class ContainersClient {
119
192
  containerId: string;
120
193
  follow?: boolean;
121
194
  }): ContainerLogsSession;
195
+ getBuildLogs(params: {
196
+ buildId: string;
197
+ follow?: boolean;
198
+ }): BuildLogsSession;
122
199
  list(params?: {
123
200
  all?: boolean;
124
201
  }): Promise<RoomContainer[]>;
@@ -20,11 +20,14 @@ function toPortPairs(values) {
20
20
  }
21
21
  function toCredentials(values) {
22
22
  return values.map((entry) => ({
23
- registry: entry.registry,
23
+ registry: entry.registry ?? null,
24
24
  username: entry.username,
25
25
  password: entry.password,
26
26
  }));
27
27
  }
28
+ function toMountList(values) {
29
+ return values.map((entry) => entry);
30
+ }
28
31
  function readStringField(data, field, operation) {
29
32
  const value = data[field];
30
33
  if (typeof value !== "string") {
@@ -32,29 +35,126 @@ function readStringField(data, field, operation) {
32
35
  }
33
36
  return value;
34
37
  }
35
- function decodeJsonStatus(data) {
36
- const text = new TextDecoder().decode(data);
38
+ function readIntegerField(data, field, operation) {
39
+ const value = data[field];
40
+ if (typeof value !== "number" || !Number.isInteger(value)) {
41
+ throw new room_server_client_1.RoomServerException(`unexpected return type from containers.${operation}`);
42
+ }
43
+ return value;
44
+ }
45
+ function readOptionalIntegerField(data, field, operation) {
46
+ const value = data[field];
47
+ if (value === undefined || value === null) {
48
+ return undefined;
49
+ }
50
+ if (typeof value !== "number" || !Number.isInteger(value)) {
51
+ throw new room_server_client_1.RoomServerException(`unexpected return type from containers.${operation}`);
52
+ }
53
+ return value;
54
+ }
55
+ function decodeUtf8(data, operation) {
56
+ try {
57
+ return new TextDecoder("utf-8", { fatal: true }).decode(data);
58
+ }
59
+ catch {
60
+ throw new room_server_client_1.RoomServerException(`containers.${operation} returned invalid UTF-8 data`);
61
+ }
62
+ }
63
+ function decodeJsonStatus(data, operation) {
64
+ const text = decodeUtf8(data, operation);
37
65
  let parsed;
38
66
  try {
39
67
  parsed = JSON.parse(text);
40
68
  }
41
69
  catch {
42
- throw new room_server_client_1.RoomServerException("containers.exec returned an invalid status payload");
70
+ throw new room_server_client_1.RoomServerException(`containers.${operation} returned an invalid status payload`);
43
71
  }
44
72
  if (!isRecord(parsed)) {
45
- throw new room_server_client_1.RoomServerException("containers.exec returned an invalid status payload");
73
+ throw new room_server_client_1.RoomServerException(`containers.${operation} returned an invalid status payload`);
46
74
  }
47
75
  const status = parsed["status"];
48
76
  if (typeof status !== "number" || !Number.isInteger(status)) {
49
- throw new room_server_client_1.RoomServerException("containers.exec returned an invalid status payload");
77
+ throw new room_server_client_1.RoomServerException(`containers.${operation} returned an invalid status payload`);
50
78
  }
51
79
  return status;
52
80
  }
81
+ function normalizeImageLabels(labelsRaw, operation) {
82
+ if (Array.isArray(labelsRaw)) {
83
+ const labels = {};
84
+ for (const entry of labelsRaw) {
85
+ if (!isRecord(entry)) {
86
+ throw new room_server_client_1.RoomServerException(`unexpected return type from containers.${operation}`);
87
+ }
88
+ const key = entry["key"];
89
+ const value = entry["value"];
90
+ if (typeof key !== "string" || typeof value !== "string") {
91
+ throw new room_server_client_1.RoomServerException(`unexpected return type from containers.${operation}`);
92
+ }
93
+ labels[key] = value;
94
+ }
95
+ return labels;
96
+ }
97
+ if (isRecord(labelsRaw)) {
98
+ const labels = {};
99
+ for (const [key, value] of Object.entries(labelsRaw)) {
100
+ if (typeof value !== "string") {
101
+ throw new room_server_client_1.RoomServerException(`unexpected return type from containers.${operation}`);
102
+ }
103
+ labels[key] = value;
104
+ }
105
+ return labels;
106
+ }
107
+ if (labelsRaw === undefined || labelsRaw === null) {
108
+ return {};
109
+ }
110
+ throw new room_server_client_1.RoomServerException(`unexpected return type from containers.${operation}`);
111
+ }
112
+ function parseImportedImage(data, operation) {
113
+ const resolvedRef = data["resolved_ref"];
114
+ const refsRaw = data["refs"];
115
+ if (typeof resolvedRef !== "string" || !Array.isArray(refsRaw) || refsRaw.some((entry) => typeof entry !== "string")) {
116
+ throw new room_server_client_1.RoomServerException(`unexpected return type from containers.${operation}`);
117
+ }
118
+ return {
119
+ resolvedRef,
120
+ refs: refsRaw,
121
+ };
122
+ }
123
+ function parseBuildJob(data, operation) {
124
+ const id = readStringField(data, "id", operation);
125
+ const tag = readStringField(data, "tag", operation);
126
+ const status = readStringField(data, "status", operation);
127
+ if (!["queued", "running", "failed", "cancelled", "succeeded"].includes(status)) {
128
+ throw new room_server_client_1.RoomServerException(`unexpected return type from containers.${operation}`);
129
+ }
130
+ return {
131
+ id,
132
+ tag,
133
+ status: status,
134
+ exitCode: readOptionalIntegerField(data, "exit_code", operation),
135
+ };
136
+ }
137
+ function buildRequestPayload(params) {
138
+ return {
139
+ tag: params.tag,
140
+ mounts: toMountList(params.mounts),
141
+ context_path: params.contextPath,
142
+ dockerfile_path: params.dockerfilePath ?? null,
143
+ private: params.private ?? false,
144
+ credentials: toCredentials(params.credentials ?? []),
145
+ context_archive_path: params.contextArchivePath ?? null,
146
+ context_archive_ref: params.contextArchiveRef ?? null,
147
+ context_archive_mount_path: params.contextArchiveMountPath ?? null,
148
+ context_archive_arch: params.contextArchiveArch ?? null,
149
+ };
150
+ }
53
151
  class ExecSession {
54
152
  constructor(params) {
55
153
  this.previousOutput = [];
154
+ this.previousError = [];
56
155
  this.resultCompleter = new completer_1.Completer();
57
156
  this.outputController = new stream_controller_1.StreamController();
157
+ this.errorController = new stream_controller_1.StreamController();
58
158
  this.queuedInput = [];
59
159
  this.inputClosed = false;
60
160
  this.closed = false;
@@ -65,6 +165,7 @@ class ExecSession {
65
165
  this.tty = params.tty;
66
166
  this.result = this.resultCompleter.fut;
67
167
  this.output = this.outputController.stream;
168
+ this.stderr = this.errorController.stream;
68
169
  }
69
170
  async *inputStream() {
70
171
  yield new response_1.BinaryContent({
@@ -119,6 +220,7 @@ class ExecSession {
119
220
  this.closed = true;
120
221
  this.closeInputStream();
121
222
  this.outputController.close();
223
+ this.errorController.close();
122
224
  }
123
225
  closeError(error) {
124
226
  if (!this.resultCompleter.completed) {
@@ -127,11 +229,16 @@ class ExecSession {
127
229
  this.closed = true;
128
230
  this.closeInputStream();
129
231
  this.outputController.close();
232
+ this.errorController.close();
130
233
  }
131
234
  addOutput(data) {
132
235
  this.previousOutput.push(data);
133
236
  this.outputController.add(data);
134
237
  }
238
+ addError(data) {
239
+ this.previousError.push(data);
240
+ this.errorController.add(data);
241
+ }
135
242
  get isClosed() {
136
243
  return this.closed;
137
244
  }
@@ -175,7 +282,7 @@ class ContainersClient {
175
282
  }
176
283
  async listImages() {
177
284
  const output = await this.invoke("list_images", {});
178
- if (!(output instanceof response_1.JsonContent)) {
285
+ if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
179
286
  throw this.unexpectedResponseError("list_images");
180
287
  }
181
288
  const imagesRaw = output.json["images"];
@@ -199,17 +306,65 @@ class ContainersClient {
199
306
  id,
200
307
  tags: normalizedTags,
201
308
  size: typeof size === "number" ? size : undefined,
202
- labels: isRecord(labelsRaw) ? labelsRaw : {},
309
+ labels: normalizeImageLabels(labelsRaw, "list_images"),
203
310
  });
204
311
  }
205
312
  return images;
206
313
  }
314
+ async deleteImage(params) {
315
+ await this.invoke("delete_image", {
316
+ image: params.image,
317
+ });
318
+ }
207
319
  async pullImage(params) {
208
320
  await this.invoke("pull_image", {
209
321
  tag: params.tag,
210
322
  credentials: toCredentials(params.credentials ?? []),
211
323
  });
212
324
  }
325
+ async pushImage(params) {
326
+ const output = await this.invoke("push_image", {
327
+ tag: params.tag,
328
+ credentials: toCredentials(params.credentials ?? []),
329
+ private: params.private ?? false,
330
+ });
331
+ if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
332
+ throw this.unexpectedResponseError("push_image");
333
+ }
334
+ return readStringField(output.json, "container_id", "push_image");
335
+ }
336
+ async load(params) {
337
+ const output = await this.invoke("load", {
338
+ archive_path: params.archivePath,
339
+ });
340
+ if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
341
+ throw this.unexpectedResponseError("load");
342
+ }
343
+ return parseImportedImage(output.json, "load");
344
+ }
345
+ async loadImage(params) {
346
+ const output = await this.invoke("load_image", {
347
+ mounts: toMountList(params.mounts),
348
+ archive_path: params.archivePath,
349
+ private: params.private ?? false,
350
+ });
351
+ if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
352
+ throw this.unexpectedResponseError("load_image");
353
+ }
354
+ return readStringField(output.json, "container_id", "load_image");
355
+ }
356
+ async saveImage(params) {
357
+ const output = await this.invoke("save_image", {
358
+ tag: params.tag,
359
+ mounts: toMountList(params.mounts),
360
+ archive_path: params.archivePath,
361
+ private: params.private ?? false,
362
+ });
363
+ if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
364
+ throw this.unexpectedResponseError("save_image");
365
+ }
366
+ return readStringField(output.json, "container_id", "save_image");
367
+ }
213
368
  async run(params) {
214
369
  const output = await this.invoke("run", {
215
370
  image: params.image,
@@ -232,6 +387,46 @@ class ContainersClient {
232
387
  }
233
388
  return readStringField(output.json, "container_id", "run");
234
389
  }
390
+ async startBuild(params) {
391
+ const output = await this.invoke("start_build", buildRequestPayload(params));
392
+ if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
393
+ throw this.unexpectedResponseError("start_build");
394
+ }
395
+ return readStringField(output.json, "build_id", "start_build");
396
+ }
397
+ async build(params) {
398
+ const output = await this.invoke("build", buildRequestPayload(params));
399
+ if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
400
+ throw this.unexpectedResponseError("build");
401
+ }
402
+ return readStringField(output.json, "build_id", "build");
403
+ }
404
+ async listBuilds() {
405
+ const output = await this.invoke("list_builds", {});
406
+ if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
407
+ throw this.unexpectedResponseError("list_builds");
408
+ }
409
+ const buildsRaw = output.json["builds"];
410
+ if (!Array.isArray(buildsRaw)) {
411
+ throw this.unexpectedResponseError("list_builds");
412
+ }
413
+ return buildsRaw.map((entry) => {
414
+ if (!isRecord(entry)) {
415
+ throw this.unexpectedResponseError("list_builds");
416
+ }
417
+ return parseBuildJob(entry, "list_builds");
418
+ });
419
+ }
420
+ async cancelBuild(params) {
421
+ await this.invoke("cancel_build", {
422
+ build_id: params.buildId,
423
+ });
424
+ }
425
+ async deleteBuild(params) {
426
+ await this.invoke("delete_build", {
427
+ build_id: params.buildId,
428
+ });
429
+ }
235
430
  async runService(params) {
236
431
  const output = await this.invoke("run_service", {
237
432
  service_id: params.serviceId,
@@ -261,6 +456,12 @@ class ContainersClient {
261
456
  if (chunk instanceof response_1.ErrorContent) {
262
457
  throw new room_server_client_1.RoomServerException(chunk.text, chunk.code);
263
458
  }
459
+ if (chunk instanceof response_1.ControlContent) {
460
+ if (chunk.method === "close") {
461
+ break;
462
+ }
463
+ throw this.unexpectedResponseError("exec");
464
+ }
264
465
  if (!(chunk instanceof response_1.BinaryContent)) {
265
466
  throw this.unexpectedResponseError("exec");
266
467
  }
@@ -272,8 +473,12 @@ class ContainersClient {
272
473
  session.addOutput(chunk.data);
273
474
  continue;
274
475
  }
476
+ if (channel === 2) {
477
+ session.addError(chunk.data);
478
+ continue;
479
+ }
275
480
  if (channel === 3) {
276
- session.close(decodeJsonStatus(chunk.data));
481
+ session.close(decodeJsonStatus(chunk.data, "exec"));
277
482
  return;
278
483
  }
279
484
  }
@@ -287,7 +492,7 @@ class ContainersClient {
287
492
  async stop(params) {
288
493
  await this.invoke("stop_container", {
289
494
  container_id: params.containerId,
290
- force: params.force ?? true,
495
+ force: params.force ?? false,
291
496
  });
292
497
  }
293
498
  async waitForExit(params) {
@@ -297,11 +502,7 @@ class ContainersClient {
297
502
  if (!(output instanceof response_1.JsonContent) || !isRecord(output.json)) {
298
503
  throw this.unexpectedResponseError("wait_for_exit");
299
504
  }
300
- const exitCode = output.json["exit_code"];
301
- if (typeof exitCode !== "number" || !Number.isInteger(exitCode)) {
302
- throw this.unexpectedResponseError("wait_for_exit");
303
- }
304
- return exitCode;
505
+ return readIntegerField(output.json, "exit_code", "wait_for_exit");
305
506
  }
306
507
  async deleteContainer(params) {
307
508
  await this.invoke("delete_container", {
@@ -342,13 +543,20 @@ class ContainersClient {
342
543
  input: inputStream(),
343
544
  })
344
545
  .then(async (stream) => {
345
- const decoder = new TextDecoder();
346
546
  for await (const chunk of stream) {
347
547
  if (chunk instanceof response_1.ErrorContent) {
348
548
  throw new room_server_client_1.RoomServerException(chunk.text, chunk.code);
349
549
  }
350
550
  if (chunk instanceof response_1.ControlContent) {
351
- continue;
551
+ if (chunk.method === "close") {
552
+ closeInputStream();
553
+ streamController.close();
554
+ if (!result.completed) {
555
+ result.complete();
556
+ }
557
+ return;
558
+ }
559
+ throw this.unexpectedResponseError("logs");
352
560
  }
353
561
  if (!(chunk instanceof response_1.BinaryContent)) {
354
562
  throw this.unexpectedResponseError("logs");
@@ -360,11 +568,125 @@ class ContainersClient {
360
568
  if (channel !== 1) {
361
569
  continue;
362
570
  }
363
- streamController.add(decoder.decode(chunk.data));
571
+ streamController.add(decodeUtf8(chunk.data, "logs"));
572
+ }
573
+ closeInputStream();
574
+ streamController.close();
575
+ if (!result.completed) {
576
+ result.complete();
577
+ }
578
+ })
579
+ .catch((error) => {
580
+ closeInputStream();
581
+ streamController.close();
582
+ if (!result.completed) {
583
+ result.completeError(error);
584
+ }
585
+ });
586
+ const outputStream = {
587
+ [Symbol.asyncIterator]() {
588
+ const it = streamController.stream[Symbol.asyncIterator]();
589
+ return {
590
+ async next() {
591
+ return await it.next();
592
+ },
593
+ async return(value) {
594
+ closeInputStream();
595
+ return await it.return?.(value) ?? { done: true, value };
596
+ },
597
+ async throw(e) {
598
+ closeInputStream();
599
+ if (it.throw) {
600
+ return await it.throw(e);
601
+ }
602
+ throw e;
603
+ },
604
+ };
605
+ },
606
+ };
607
+ return {
608
+ stream: outputStream,
609
+ result: result.fut,
610
+ cancel: async () => {
611
+ closeInputStream();
612
+ await result.fut.catch(() => undefined);
613
+ },
614
+ };
615
+ }
616
+ getBuildLogs(params) {
617
+ const requestId = (0, uuid_1.v4)();
618
+ const closeInput = new completer_1.Completer();
619
+ const streamController = new stream_controller_1.StreamController();
620
+ const result = new completer_1.Completer();
621
+ let inputClosed = false;
622
+ const closeInputStream = () => {
623
+ if (inputClosed) {
624
+ return;
625
+ }
626
+ inputClosed = true;
627
+ if (!closeInput.completed) {
628
+ closeInput.complete();
629
+ }
630
+ };
631
+ const inputStream = async function* () {
632
+ yield new response_1.BinaryContent({
633
+ data: new Uint8Array(0),
634
+ headers: {
635
+ kind: "start",
636
+ request_id: requestId,
637
+ build_id: params.buildId,
638
+ follow: params.follow ?? true,
639
+ },
640
+ });
641
+ await closeInput.fut;
642
+ };
643
+ this.room
644
+ .invokeStream({
645
+ toolkit: "containers",
646
+ tool: "get_build_logs",
647
+ input: inputStream(),
648
+ })
649
+ .then(async (stream) => {
650
+ for await (const chunk of stream) {
651
+ if (chunk instanceof response_1.ErrorContent) {
652
+ throw new room_server_client_1.RoomServerException(chunk.text, chunk.code);
653
+ }
654
+ if (chunk instanceof response_1.ControlContent) {
655
+ if (chunk.method === "close") {
656
+ closeInputStream();
657
+ streamController.close();
658
+ if (!result.completed) {
659
+ result.complete(null);
660
+ }
661
+ return;
662
+ }
663
+ throw this.unexpectedResponseError("get_build_logs");
664
+ }
665
+ if (!(chunk instanceof response_1.BinaryContent)) {
666
+ throw this.unexpectedResponseError("get_build_logs");
667
+ }
668
+ const channel = chunk.headers["channel"];
669
+ if (typeof channel !== "number") {
670
+ throw new room_server_client_1.RoomServerException("containers.get_build_logs returned a chunk without a valid channel");
671
+ }
672
+ if (channel === 1) {
673
+ streamController.add(decodeUtf8(chunk.data, "get_build_logs"));
674
+ continue;
675
+ }
676
+ if (channel === 3) {
677
+ closeInputStream();
678
+ streamController.close();
679
+ if (!result.completed) {
680
+ result.complete(decodeJsonStatus(chunk.data, "get_build_logs"));
681
+ }
682
+ return;
683
+ }
364
684
  }
365
685
  closeInputStream();
366
686
  streamController.close();
367
- result.complete();
687
+ if (!result.completed) {
688
+ result.complete(null);
689
+ }
368
690
  })
369
691
  .catch((error) => {
370
692
  closeInputStream();