@meshagent/meshagent-agents 0.43.0

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 (55) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE +1 -0
  3. package/README.md +206 -0
  4. package/dist/browser/agent-messages.d.ts +343 -0
  5. package/dist/browser/agent-messages.js +1015 -0
  6. package/dist/browser/change-emitter.d.ts +8 -0
  7. package/dist/browser/change-emitter.js +20 -0
  8. package/dist/browser/chat-client.d.ts +225 -0
  9. package/dist/browser/chat-client.js +642 -0
  10. package/dist/browser/images-dataset.d.ts +83 -0
  11. package/dist/browser/images-dataset.js +282 -0
  12. package/dist/browser/index.d.ts +6 -0
  13. package/dist/browser/index.js +22 -0
  14. package/dist/browser/package.json +1 -0
  15. package/dist/browser/room-shell-toolkit.d.ts +14 -0
  16. package/dist/browser/room-shell-toolkit.js +274 -0
  17. package/dist/browser/room-storage-toolkit.d.ts +11 -0
  18. package/dist/browser/room-storage-toolkit.js +513 -0
  19. package/dist/browser/thread-storage.d.ts +102 -0
  20. package/dist/browser/thread-storage.js +476 -0
  21. package/dist/esm/agent-messages.d.ts +343 -0
  22. package/dist/esm/agent-messages.js +989 -0
  23. package/dist/esm/change-emitter.d.ts +8 -0
  24. package/dist/esm/change-emitter.js +16 -0
  25. package/dist/esm/chat-client.d.ts +225 -0
  26. package/dist/esm/chat-client.js +632 -0
  27. package/dist/esm/images-dataset.d.ts +83 -0
  28. package/dist/esm/images-dataset.js +274 -0
  29. package/dist/esm/index.d.ts +6 -0
  30. package/dist/esm/index.js +6 -0
  31. package/dist/esm/package.json +1 -0
  32. package/dist/esm/room-shell-toolkit.d.ts +14 -0
  33. package/dist/esm/room-shell-toolkit.js +270 -0
  34. package/dist/esm/room-storage-toolkit.d.ts +11 -0
  35. package/dist/esm/room-storage-toolkit.js +509 -0
  36. package/dist/esm/thread-storage.d.ts +102 -0
  37. package/dist/esm/thread-storage.js +465 -0
  38. package/dist/node/agent-messages.d.ts +343 -0
  39. package/dist/node/agent-messages.js +1015 -0
  40. package/dist/node/change-emitter.d.ts +8 -0
  41. package/dist/node/change-emitter.js +20 -0
  42. package/dist/node/chat-client.d.ts +225 -0
  43. package/dist/node/chat-client.js +642 -0
  44. package/dist/node/images-dataset.d.ts +83 -0
  45. package/dist/node/images-dataset.js +282 -0
  46. package/dist/node/index.d.ts +6 -0
  47. package/dist/node/index.js +22 -0
  48. package/dist/node/package.json +1 -0
  49. package/dist/node/room-shell-toolkit.d.ts +14 -0
  50. package/dist/node/room-shell-toolkit.js +274 -0
  51. package/dist/node/room-storage-toolkit.d.ts +11 -0
  52. package/dist/node/room-storage-toolkit.js +513 -0
  53. package/dist/node/thread-storage.d.ts +102 -0
  54. package/dist/node/thread-storage.js +476 -0
  55. package/package.json +57 -0
@@ -0,0 +1,83 @@
1
+ import { Schema } from "apache-arrow";
2
+ import { type RoomClient } from "@meshagent/meshagent";
3
+ export declare class SavedImage {
4
+ readonly id: string;
5
+ readonly mimeType: string;
6
+ readonly createdAt: Date;
7
+ readonly createdBy: string;
8
+ readonly annotations: Readonly<Record<string, string>>;
9
+ constructor({ id, mimeType, createdAt, createdBy, annotations, }: {
10
+ id: string;
11
+ mimeType: string;
12
+ createdAt: Date;
13
+ createdBy: string;
14
+ annotations: Record<string, string>;
15
+ });
16
+ }
17
+ export declare class ImageDatasetRecord {
18
+ readonly data: Uint8Array;
19
+ readonly mimeType: string;
20
+ constructor({ data, mimeType }: {
21
+ data: Uint8Array;
22
+ mimeType: string;
23
+ });
24
+ }
25
+ export declare class DatasetUriReference {
26
+ readonly table: string;
27
+ readonly namespace?: string[];
28
+ readonly id: string;
29
+ constructor({ table, namespace, id }: {
30
+ table: string;
31
+ namespace?: string[];
32
+ id: string;
33
+ });
34
+ }
35
+ export declare class ImageDatasetClient {
36
+ readonly room: RoomClient;
37
+ readonly table: string;
38
+ constructor({ room, table }: {
39
+ room: RoomClient;
40
+ table?: string;
41
+ });
42
+ static datasetUriReference(uri: string): DatasetUriReference;
43
+ readRecord(imageId: string, { table, namespace, fallbackMimeType, }?: {
44
+ table?: string;
45
+ namespace?: string[];
46
+ fallbackMimeType?: string;
47
+ }): Promise<ImageDatasetRecord | null>;
48
+ readRecordFromUri(uri: string, { fallbackMimeType }?: {
49
+ fallbackMimeType?: string;
50
+ }): Promise<ImageDatasetRecord | null>;
51
+ }
52
+ export declare class ImagesDataset extends ImageDatasetClient {
53
+ static readonly tableName = "images";
54
+ static readonly schema: Schema<any>;
55
+ readonly namespace?: string[];
56
+ private ready?;
57
+ constructor({ room, table, namespace }: {
58
+ room: RoomClient;
59
+ table?: string;
60
+ namespace?: string[];
61
+ });
62
+ save({ data, mimeType, createdBy, annotations, imageId, createdAt, }: {
63
+ data: Uint8Array;
64
+ mimeType: string;
65
+ createdBy: string;
66
+ annotations?: Record<string, string>;
67
+ imageId?: string;
68
+ createdAt?: Date;
69
+ }): Promise<SavedImage>;
70
+ read(imageId: string): Promise<SavedImage | null>;
71
+ search({ where, limit }?: {
72
+ where?: unknown;
73
+ limit?: number;
74
+ }): Promise<SavedImage[]>;
75
+ readRecord(imageId: string, { table, namespace, fallbackMimeType, }?: {
76
+ table?: string;
77
+ namespace?: string[];
78
+ fallbackMimeType?: string;
79
+ }): Promise<ImageDatasetRecord | null>;
80
+ private ensureReady;
81
+ private createTable;
82
+ private batchFor;
83
+ }
@@ -0,0 +1,274 @@
1
+ import { Field, LargeBinary, List, Schema, Struct, TimestampMicrosecond, Utf8, tableFromArrays, } from "apache-arrow";
2
+ import { RoomServerException, } from "@meshagent/meshagent";
3
+ import { v4 as uuidv4 } from "uuid";
4
+ export class SavedImage {
5
+ constructor({ id, mimeType, createdAt, createdBy, annotations, }) {
6
+ this.id = id;
7
+ this.mimeType = mimeType;
8
+ this.createdAt = createdAt;
9
+ this.createdBy = createdBy;
10
+ this.annotations = Object.freeze({ ...annotations });
11
+ }
12
+ }
13
+ export class ImageDatasetRecord {
14
+ constructor({ data, mimeType }) {
15
+ this.data = data;
16
+ this.mimeType = mimeType;
17
+ }
18
+ }
19
+ export class DatasetUriReference {
20
+ constructor({ table, namespace, id }) {
21
+ this.table = table;
22
+ this.namespace = namespace;
23
+ this.id = id;
24
+ }
25
+ }
26
+ export class ImageDatasetClient {
27
+ constructor({ room, table = ImagesDataset.tableName }) {
28
+ this.room = room;
29
+ this.table = table;
30
+ }
31
+ static datasetUriReference(uri) {
32
+ const parsed = new URL(uri);
33
+ if (parsed.protocol !== "dataset:") {
34
+ throw new TypeError("dataset image URI must use the dataset:// scheme");
35
+ }
36
+ const id = parsed.searchParams.get("id")?.trim();
37
+ if (id == null || id === "") {
38
+ throw new TypeError("dataset image URI must include an id query parameter");
39
+ }
40
+ const segments = [
41
+ ...(parsed.hostname.trim() !== "" ? [parsed.hostname.trim()] : []),
42
+ ...parsed.pathname.split("/").map((segment) => segment.trim()).filter((segment) => segment !== ""),
43
+ ];
44
+ if (segments.length === 0) {
45
+ throw new TypeError("dataset image URI must include a table path");
46
+ }
47
+ return new DatasetUriReference({
48
+ table: segments[segments.length - 1],
49
+ namespace: segments.length === 1 ? undefined : segments.slice(0, -1),
50
+ id,
51
+ });
52
+ }
53
+ async readRecord(imageId, { table, namespace, fallbackMimeType, } = {}) {
54
+ const normalizedId = normalizeImageId(imageId);
55
+ const rows = await this.room.datasets.search({
56
+ table: table ?? this.table,
57
+ where: { id: normalizedId },
58
+ limit: 1,
59
+ select: ["data", "mime_type"],
60
+ namespace,
61
+ });
62
+ const record = rowsFromTable(rows)[0];
63
+ if (record == null) {
64
+ return null;
65
+ }
66
+ const data = record.data;
67
+ const bytes = data instanceof Uint8Array ? data : Array.isArray(data) ? Uint8Array.from(data) : null;
68
+ if (bytes == null) {
69
+ return null;
70
+ }
71
+ const rawMimeType = record.mime_type;
72
+ return new ImageDatasetRecord({
73
+ data: bytes,
74
+ mimeType: typeof rawMimeType === "string" && rawMimeType.trim() !== "" ? rawMimeType.trim() : fallbackMimeType ?? "image/png",
75
+ });
76
+ }
77
+ readRecordFromUri(uri, { fallbackMimeType } = {}) {
78
+ const reference = ImageDatasetClient.datasetUriReference(uri);
79
+ return this.readRecord(reference.id, {
80
+ table: reference.table,
81
+ namespace: reference.namespace,
82
+ fallbackMimeType,
83
+ });
84
+ }
85
+ }
86
+ export class ImagesDataset extends ImageDatasetClient {
87
+ constructor({ room, table = ImagesDataset.tableName, namespace }) {
88
+ super({ room, table });
89
+ this.namespace = namespace;
90
+ }
91
+ async save({ data, mimeType, createdBy, annotations = {}, imageId, createdAt, }) {
92
+ const normalizedId = imageId != null && imageId.trim() !== "" ? imageId.trim() : uuidv4();
93
+ const normalizedMimeType = mimeType.trim();
94
+ if (normalizedMimeType === "") {
95
+ throw new TypeError("image MIME type cannot be empty");
96
+ }
97
+ const normalizedCreatedBy = createdBy.trim();
98
+ if (normalizedCreatedBy === "") {
99
+ throw new TypeError("creator cannot be empty");
100
+ }
101
+ const timestamp = createdAt ?? new Date();
102
+ const saved = new SavedImage({
103
+ id: normalizedId,
104
+ mimeType: normalizedMimeType,
105
+ createdAt: timestamp,
106
+ createdBy: normalizedCreatedBy,
107
+ annotations,
108
+ });
109
+ await this.ensureReady();
110
+ await retryCommitConflicts(() => this.room.datasets.insert({
111
+ table: this.table,
112
+ records: this.batchFor([{ image: saved, data }]),
113
+ namespace: this.namespace,
114
+ }));
115
+ return saved;
116
+ }
117
+ async read(imageId) {
118
+ await this.ensureReady();
119
+ const rows = await this.room.datasets.search({
120
+ table: this.table,
121
+ where: { id: normalizeImageId(imageId) },
122
+ limit: 1,
123
+ select: ["id", "mime_type", "created_at", "created_by"],
124
+ namespace: this.namespace,
125
+ });
126
+ const row = rowsFromTable(rows)[0];
127
+ return row == null ? null : savedImageFromRow(row);
128
+ }
129
+ async search({ where, limit } = {}) {
130
+ await this.ensureReady();
131
+ const rows = await this.room.datasets.search({
132
+ table: this.table,
133
+ where: where,
134
+ limit,
135
+ select: ["id", "mime_type", "created_at", "created_by"],
136
+ namespace: this.namespace,
137
+ });
138
+ return rowsFromTable(rows).map(savedImageFromRow).filter((entry) => entry != null);
139
+ }
140
+ async readRecord(imageId, { table, namespace, fallbackMimeType, } = {}) {
141
+ await this.ensureReady();
142
+ return super.readRecord(imageId, {
143
+ table: table ?? this.table,
144
+ namespace: namespace ?? this.namespace,
145
+ fallbackMimeType,
146
+ });
147
+ }
148
+ ensureReady() {
149
+ this.ready ?? (this.ready = this.createTable());
150
+ return this.ready;
151
+ }
152
+ async createTable() {
153
+ await this.room.datasets.createTableWithSchema({
154
+ name: this.table,
155
+ schema: ImagesDataset.schema,
156
+ mode: "create_if_not_exists",
157
+ namespace: this.namespace,
158
+ });
159
+ try {
160
+ await this.room.datasets.createIndex({
161
+ table: this.table,
162
+ config: { column: "id", index_type: "BTREE" },
163
+ namespace: this.namespace,
164
+ });
165
+ }
166
+ catch {
167
+ }
168
+ }
169
+ batchFor(rows) {
170
+ return tableFromArrays({
171
+ id: rows.map((row) => row.image.id),
172
+ data: rows.map((row) => row.data),
173
+ mime_type: rows.map((row) => row.image.mimeType),
174
+ created_at: rows.map((row) => row.image.createdAt),
175
+ created_by: rows.map((row) => row.image.createdBy),
176
+ annotations: rows.map((row) => Object.entries(row.image.annotations).map(([key, value]) => ({ key, value }))),
177
+ });
178
+ }
179
+ }
180
+ ImagesDataset.tableName = "images";
181
+ ImagesDataset.schema = new Schema([
182
+ new Field("id", new Utf8(), false),
183
+ new Field("data", new LargeBinary(), false, new Map([["content-type", "image/*"]])),
184
+ new Field("mime_type", new Utf8(), false),
185
+ new Field("created_at", new TimestampMicrosecond("UTC"), false),
186
+ new Field("created_by", new Utf8(), false),
187
+ new Field("annotations", new List(new Field("item", new Struct([
188
+ new Field("key", new Utf8(), false),
189
+ new Field("value", new Utf8(), true),
190
+ ]), true)), true),
191
+ ]);
192
+ function normalizeImageId(imageId) {
193
+ const normalized = imageId.trim();
194
+ if (normalized === "") {
195
+ throw new TypeError("image id cannot be empty");
196
+ }
197
+ return normalized;
198
+ }
199
+ function savedImageFromRow(row) {
200
+ const id = stringFromDatasetValue(row.id);
201
+ const mimeType = stringFromDatasetValue(row.mime_type);
202
+ const createdBy = stringFromDatasetValue(row.created_by);
203
+ if (id == null || mimeType == null || createdBy == null) {
204
+ return null;
205
+ }
206
+ return new SavedImage({
207
+ id,
208
+ mimeType,
209
+ createdAt: dateTimeFromDatasetValue(row.created_at) ?? new Date(0),
210
+ createdBy,
211
+ annotations: annotationsFromValue(row.annotations),
212
+ });
213
+ }
214
+ function dateTimeFromDatasetValue(value) {
215
+ if (value instanceof Date) {
216
+ return value;
217
+ }
218
+ if (typeof value === "string") {
219
+ const parsed = new Date(value);
220
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
221
+ }
222
+ if (typeof value === "number") {
223
+ return new Date(Math.trunc(value / 1000));
224
+ }
225
+ if (typeof value === "bigint") {
226
+ return new Date(Number(value / 1000n));
227
+ }
228
+ return null;
229
+ }
230
+ function stringFromDatasetValue(value) {
231
+ if (value == null) {
232
+ return null;
233
+ }
234
+ return typeof value === "string" ? value : String(value);
235
+ }
236
+ function annotationsFromValue(value) {
237
+ if (!Array.isArray(value)) {
238
+ return {};
239
+ }
240
+ const annotations = {};
241
+ for (const item of value) {
242
+ if (item != null && typeof item === "object" && !Array.isArray(item)) {
243
+ const raw = item;
244
+ if (typeof raw.key === "string" && typeof raw.value === "string") {
245
+ annotations[raw.key] = raw.value;
246
+ }
247
+ }
248
+ }
249
+ return annotations;
250
+ }
251
+ async function retryCommitConflicts(action) {
252
+ for (let attempt = 0; attempt < 6; attempt += 1) {
253
+ try {
254
+ await action();
255
+ return;
256
+ }
257
+ catch (error) {
258
+ if (!(error instanceof RoomServerException)) {
259
+ throw error;
260
+ }
261
+ const retryable = error.retryable ||
262
+ error.message.toLowerCase().includes("commit conflict") ||
263
+ error.message.toLowerCase().includes("conflicting commit");
264
+ if (!retryable || attempt === 5) {
265
+ throw error;
266
+ }
267
+ await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
268
+ }
269
+ }
270
+ }
271
+ function rowsFromTable(table) {
272
+ const tables = Array.isArray(table) ? table : [table];
273
+ return tables.flatMap((entry) => entry.toArray().map((row) => ({ ...row })));
274
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./agent-messages.js";
2
+ export * from "./chat-client.js";
3
+ export * from "./images-dataset.js";
4
+ export * from "./room-shell-toolkit.js";
5
+ export * from "./room-storage-toolkit.js";
6
+ export * from "./thread-storage.js";
@@ -0,0 +1,6 @@
1
+ export * from "./agent-messages.js";
2
+ export * from "./chat-client.js";
3
+ export * from "./images-dataset.js";
4
+ export * from "./room-shell-toolkit.js";
5
+ export * from "./room-storage-toolkit.js";
6
+ export * from "./thread-storage.js";
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,14 @@
1
+ import { Meshagent, Toolkit, type ContainerMountSpec } from "@meshagent/meshagent";
2
+ export declare const defaultRoomShellMaxOutputLength: number;
3
+ export declare const defaultRoomShellMounts: ContainerMountSpec;
4
+ export declare class RoomShellToolkit extends Toolkit {
5
+ constructor({ client, projectId, rooms, image, workingDir, mounts, env, }: {
6
+ client: Meshagent;
7
+ projectId: string;
8
+ rooms: string[];
9
+ image?: string;
10
+ workingDir?: string;
11
+ mounts?: ContainerMountSpec;
12
+ env?: Record<string, string>;
13
+ });
14
+ }
@@ -0,0 +1,270 @@
1
+ import { JsonContent, RoomClient, RoomServerException, Tool, Toolkit, ToolContentSpec, WebSocketClientProtocol, } from "@meshagent/meshagent";
2
+ export const defaultRoomShellMaxOutputLength = 50 * 1024;
3
+ export const defaultRoomShellMounts = {
4
+ room: [{ path: "/data" }],
5
+ };
6
+ export class RoomShellToolkit extends Toolkit {
7
+ constructor({ client, projectId, rooms, image = "meshagent/python:default", workingDir, mounts = defaultRoomShellMounts, env = {}, }) {
8
+ super({
9
+ name: "room_shell",
10
+ title: "room shell",
11
+ description: "execute shell commands in containers for explicitly shared rooms",
12
+ tools: [
13
+ new RoomShellTool(new RoomShellToolkitConfig({
14
+ client,
15
+ projectId,
16
+ rooms: normalizeRooms(rooms),
17
+ image,
18
+ workingDir,
19
+ mounts,
20
+ env,
21
+ })),
22
+ ],
23
+ });
24
+ }
25
+ }
26
+ class RoomShellToolkitConfig {
27
+ constructor({ client, projectId, rooms, image, workingDir, mounts, env, }) {
28
+ this.containerIdsByRoom = new Map();
29
+ if (rooms.length === 0) {
30
+ throw new TypeError("RoomShellToolkit requires at least one allowed room.");
31
+ }
32
+ if (image.trim() === "") {
33
+ throw new TypeError("image must not be empty.");
34
+ }
35
+ this.client = client;
36
+ this.projectId = projectId;
37
+ this.rooms = rooms;
38
+ this.allowedRooms = new Set(rooms);
39
+ this.image = image;
40
+ this.workingDir = workingDir;
41
+ this.mounts = mounts;
42
+ this.env = { ...env };
43
+ }
44
+ requireRoom(value) {
45
+ if (typeof value !== "string" || value.trim() === "") {
46
+ throw new RoomServerException("room_name is required.");
47
+ }
48
+ const roomName = value.trim();
49
+ if (!this.allowedRooms.has(roomName)) {
50
+ throw new RoomServerException(`room '${roomName}' is not shared with this toolkit.`);
51
+ }
52
+ return roomName;
53
+ }
54
+ async withRoom(roomName, callback) {
55
+ const connection = await this.client.connectRoom(this.projectId, roomName);
56
+ const room = new RoomClient({
57
+ protocolFactory: WebSocketClientProtocol.createFactory({
58
+ url: connection.roomUrl,
59
+ token: connection.jwt,
60
+ }),
61
+ reconnectTimeout: 0,
62
+ });
63
+ try {
64
+ await room.start();
65
+ await room.ready;
66
+ return await callback(room);
67
+ }
68
+ finally {
69
+ room.dispose();
70
+ }
71
+ }
72
+ async getContainerId(room, roomName) {
73
+ const cached = this.containerIdsByRoom.get(roomName);
74
+ if (cached != null) {
75
+ const containers = await room.containers.list({});
76
+ if (containers.some((container) => container.id === cached)) {
77
+ return cached;
78
+ }
79
+ }
80
+ const containerId = await room.containers.run({
81
+ image: this.image,
82
+ command: "sleep infinity",
83
+ mounts: this.mounts,
84
+ writableRootFs: true,
85
+ env: this.env,
86
+ });
87
+ this.containerIdsByRoom.set(roomName, containerId);
88
+ return containerId;
89
+ }
90
+ }
91
+ class RoomShellTool extends Tool {
92
+ constructor(config) {
93
+ super({
94
+ name: "container_shell",
95
+ title: "container shell",
96
+ description: "execute shell commands in a container for explicitly shared room storage",
97
+ inputSchema: {
98
+ type: "object",
99
+ additionalProperties: false,
100
+ required: [
101
+ "room_name",
102
+ "commands",
103
+ "max_output_length",
104
+ "timeout_ms",
105
+ ],
106
+ properties: {
107
+ room_name: {
108
+ type: "string",
109
+ enum: config.rooms,
110
+ description: "The shared room where the shell should run.",
111
+ },
112
+ commands: {
113
+ type: "array",
114
+ minItems: 1,
115
+ items: { type: "string" },
116
+ description: "Shell commands to execute.",
117
+ },
118
+ max_output_length: {
119
+ type: ["integer", "null"],
120
+ description: "Optional maximum number of characters to return per output stream.",
121
+ },
122
+ timeout_ms: {
123
+ type: ["integer", "null"],
124
+ description: "Optional timeout per command in milliseconds.",
125
+ },
126
+ },
127
+ },
128
+ outputSpec: new ToolContentSpec({ types: ["json"], stream: false }),
129
+ });
130
+ this.config = config;
131
+ }
132
+ async execute(args) {
133
+ const roomName = this.config.requireRoom(args.room_name);
134
+ const commands = requiredStringList(args.commands, "commands");
135
+ const maxOutputLength = optionalPositiveInt(args.max_output_length, "max_output_length") ??
136
+ defaultRoomShellMaxOutputLength;
137
+ const timeoutMs = optionalPositiveInt(args.timeout_ms, "timeout_ms") ?? 60000;
138
+ return await this.config.withRoom(roomName, async (room) => {
139
+ const containerId = await this.config.getContainerId(room, roomName);
140
+ const results = [];
141
+ for (const command of commands) {
142
+ const commandToRun = commandWithWorkingDir(command, this.config.workingDir);
143
+ const session = room.containers.exec({
144
+ containerId,
145
+ command: `bash -lc ${shellQuote(commandToRun)}`,
146
+ });
147
+ const stdout = new OutputAccumulator(maxOutputLength);
148
+ const stderr = new OutputAccumulator(maxOutputLength);
149
+ const stdoutTask = collectOutput(session.output, stdout);
150
+ const stderrTask = collectOutput(session.stderr, stderr);
151
+ try {
152
+ const exitCode = await timeout(session.result, timeoutMs);
153
+ await finishOutputTasks(stdoutTask, stderrTask);
154
+ results.push({
155
+ outcome: { type: "exit", exit_code: exitCode },
156
+ stdout: stdout.finish(),
157
+ stderr: stderr.finish(),
158
+ });
159
+ }
160
+ catch (error) {
161
+ if (error instanceof TimeoutError) {
162
+ await session.kill();
163
+ await finishOutputTasks(stdoutTask, stderrTask);
164
+ results.push({
165
+ outcome: { type: "timeout" },
166
+ stdout: stdout.finish(),
167
+ stderr: stderr.finish(),
168
+ });
169
+ }
170
+ else {
171
+ await finishOutputTasks(stdoutTask, stderrTask);
172
+ results.push({
173
+ outcome: { type: "exit", exit_code: 1 },
174
+ stdout: stdout.finish(),
175
+ stderr: String(error),
176
+ });
177
+ }
178
+ break;
179
+ }
180
+ }
181
+ return new JsonContent({ json: { results } });
182
+ });
183
+ }
184
+ }
185
+ async function finishOutputTasks(...tasks) {
186
+ for (const task of tasks) {
187
+ try {
188
+ await task;
189
+ }
190
+ catch {
191
+ }
192
+ }
193
+ }
194
+ function normalizeRooms(rooms) {
195
+ return [
196
+ ...new Set(rooms.map((room) => room.trim()).filter((room) => room !== "")),
197
+ ];
198
+ }
199
+ function requiredStringList(value, field) {
200
+ if (!Array.isArray(value) || value.length === 0) {
201
+ throw new RoomServerException(`${field} must be a non-empty string array.`);
202
+ }
203
+ const output = [];
204
+ for (const item of value) {
205
+ if (typeof item !== "string" || item === "") {
206
+ throw new RoomServerException(`${field} must be a non-empty string array.`);
207
+ }
208
+ output.push(item);
209
+ }
210
+ return output;
211
+ }
212
+ function optionalPositiveInt(value, field) {
213
+ if (value == null)
214
+ return undefined;
215
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
216
+ throw new RoomServerException(`${field} must be a positive integer.`);
217
+ }
218
+ return value;
219
+ }
220
+ function commandWithWorkingDir(command, workingDir) {
221
+ return workingDir == null || workingDir.trim() === ""
222
+ ? command
223
+ : `cd ${shellQuote(workingDir)} && ${command}`;
224
+ }
225
+ function shellQuote(value) {
226
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
227
+ }
228
+ async function collectOutput(stream, accumulator) {
229
+ for await (const chunk of stream) {
230
+ accumulator.add(chunk);
231
+ }
232
+ }
233
+ class OutputAccumulator {
234
+ constructor(maxLength) {
235
+ this.maxLength = maxLength;
236
+ this.buffer = "";
237
+ this.truncated = false;
238
+ }
239
+ add(data) {
240
+ if (this.buffer.length >= this.maxLength) {
241
+ this.truncated = true;
242
+ return;
243
+ }
244
+ const text = new TextDecoder().decode(data);
245
+ const remaining = this.maxLength - this.buffer.length;
246
+ if (text.length <= remaining) {
247
+ this.buffer += text;
248
+ return;
249
+ }
250
+ this.buffer += text.slice(0, remaining);
251
+ this.truncated = true;
252
+ }
253
+ finish() {
254
+ return this.truncated ? `${this.buffer}\n... truncated ...` : this.buffer;
255
+ }
256
+ }
257
+ class TimeoutError extends Error {
258
+ }
259
+ function timeout(promise, ms) {
260
+ return new Promise((resolve, reject) => {
261
+ const timer = setTimeout(() => reject(new TimeoutError()), ms);
262
+ promise.then((value) => {
263
+ clearTimeout(timer);
264
+ resolve(value);
265
+ }, (error) => {
266
+ clearTimeout(timer);
267
+ reject(error);
268
+ });
269
+ });
270
+ }
@@ -0,0 +1,11 @@
1
+ import { Meshagent, Toolkit } from "@meshagent/meshagent";
2
+ export declare const defaultRoomStorageToolkitMaxLength = 20000;
3
+ export declare class RoomStorageToolkit extends Toolkit {
4
+ constructor({ client, projectId, rooms, readOnly, maxLength, }: {
5
+ client: Meshagent;
6
+ projectId: string;
7
+ rooms: string[];
8
+ readOnly?: boolean;
9
+ maxLength?: number;
10
+ });
11
+ }