@zooid/transport-matrix 0.7.4 → 0.8.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.
package/dist/index.d.ts CHANGED
@@ -221,7 +221,13 @@ interface AgentBinding {
221
221
  */
222
222
  rooms: RoomBinding[];
223
223
  trigger: 'mention' | 'any';
224
+ /** Host path of the agent's workspace (resolved agent.workdir). Media files land here. */
225
+ workspaceDir?: string;
226
+ /** Path prefix as the agent sees it: '/workspace' for containers, = workspaceDir for local. */
227
+ agentWorkspacePath?: string;
224
228
  }
229
+ declare const MEDIA_MSGTYPES: Set<string>;
230
+ declare function isMediaMsgtype(t: string | undefined): boolean;
225
231
  interface ThreadState {
226
232
  /** Agent names that have posted in this thread, in order. */
227
233
  participants: string[];
@@ -266,6 +272,36 @@ declare class BotPool {
266
272
  findByName(name: string): AgentBinding | undefined;
267
273
  }
268
274
 
275
+ interface WriteAttachmentInput {
276
+ workspaceDir: string;
277
+ agentWorkspacePath: string;
278
+ eventId: string;
279
+ filename: string;
280
+ data: Uint8Array;
281
+ }
282
+ declare function writeAttachment(input: WriteAttachmentInput): {
283
+ hostPath: string;
284
+ agentPath: string;
285
+ };
286
+
287
+ interface MediaClientLike {
288
+ download(input: {
289
+ mxcUri: string;
290
+ asUserId: string;
291
+ maxBytes?: number;
292
+ }): Promise<{
293
+ data: Uint8Array;
294
+ contentType: string;
295
+ }>;
296
+ upload(input: {
297
+ data: Uint8Array;
298
+ contentType: string;
299
+ filename?: string;
300
+ asUserId: string;
301
+ }): Promise<{
302
+ content_uri: string;
303
+ }>;
304
+ }
269
305
  interface CreateMatrixTransportOptions {
270
306
  agents: AcpRegistry;
271
307
  approvals: ApprovalCorrelator;
@@ -280,6 +316,10 @@ interface CreateMatrixTransportOptions {
280
316
  drainQuietMs?: number;
281
317
  /** Hard cap on the post-turn drain. Defaults to `DRAIN_MAX_MS`. */
282
318
  drainMaxMs?: number;
319
+ /** Injected media client for downloading/uploading Matrix media. */
320
+ media?: MediaClientLike;
321
+ /** Injected attachment writer (defaults to the real writeAttachment). */
322
+ writeAttachmentFn?: typeof writeAttachment;
283
323
  }
284
324
  declare function createMatrixTransport(opts: CreateMatrixTransportOptions): {
285
325
  app: Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
@@ -304,6 +344,15 @@ interface EnsureSpaceOpts {
304
344
  * if the alias already resolves we return the existing room untouched.
305
345
  */
306
346
  admins?: string[];
347
+ /**
348
+ * Join rule pinned on the space at creation. Defaults to `invite`: a
349
+ * workspace is joined by invitation, not self-service, so it can't be walked
350
+ * into (which would otherwise satisfy every restricted child room's `allow`).
351
+ * `zooid dev` passes `public` so a self-service-registered local account can
352
+ * join `#<space>` straight from the web client without an invite — acceptable
353
+ * because the dev homeserver is local-only and never deployed.
354
+ */
355
+ joinRule?: 'invite' | 'public';
307
356
  }
308
357
  declare function ensureWorkforceSpace(opts: EnsureSpaceOpts): Promise<string>;
309
358
  interface EnsureDefaultChannelOpts {
@@ -359,4 +408,63 @@ interface StartOpts {
359
408
  }
360
409
  declare function startWorkforcePublisher(opts: StartOpts): Promise<PublisherHandle>;
361
410
 
362
- export { type AgentBinding, BotPool, type CreateMatrixTransportOptions, type EnsureDefaultChannelOpts, type EnsureSpaceOpts, MatrixClient, type MatrixClientOptions, MatrixContextProvider, type MatrixContextProviderOpts, type MatrixTransportConfig, type MaybeMessage, type PublishOpts, type PublisherHandle, type RouteMatch, type SendCustomEventInput, type SendMessageInput, type StartOpts as StartWorkforcePublisherOpts, type WorkforceRoster, buildWorkforceRoster, createMatrixTransport, ensureDefaultChannel, ensureWorkforceSpace, extractMentions, publishWorkforce, renderRegistration, route, serverNameFromMxid, startWorkforcePublisher };
411
+ /** Limits are routing policy, not enforcement see ZOD057 (enforcement lives
412
+ * in Tuwunel config + the Zoon composer). */
413
+ declare const MAX_INLINE_IMAGE_BYTES = 524288;
414
+ declare const INLINE_IMAGE_MIMES: string[];
415
+ declare const MAX_DOWNLOAD_BYTES = 33554432;
416
+ interface MediaClientOptions {
417
+ homeserver: string;
418
+ asToken: string;
419
+ fetch?: typeof globalThis.fetch;
420
+ }
421
+ declare function parseMxcUri(uri: string): {
422
+ serverName: string;
423
+ mediaId: string;
424
+ } | null;
425
+ declare class MediaClient {
426
+ private readonly homeserver;
427
+ private readonly asToken;
428
+ private readonly fetch;
429
+ constructor(opts: MediaClientOptions);
430
+ download(input: {
431
+ mxcUri: string;
432
+ asUserId: string;
433
+ maxBytes?: number;
434
+ }): Promise<{
435
+ data: Uint8Array;
436
+ contentType: string;
437
+ }>;
438
+ upload(input: {
439
+ data: Uint8Array;
440
+ contentType: string;
441
+ filename?: string;
442
+ asUserId: string;
443
+ }): Promise<{
444
+ content_uri: string;
445
+ }>;
446
+ }
447
+
448
+ declare const MAX_MEDIA_PER_TURN = 8;
449
+ interface PendingMediaItem {
450
+ eventId: string;
451
+ sender: string;
452
+ msgtype: string;
453
+ body: string;
454
+ filename?: string;
455
+ url: string;
456
+ info?: {
457
+ mimetype?: string;
458
+ size?: number;
459
+ w?: number;
460
+ h?: number;
461
+ };
462
+ }
463
+ declare class PendingMediaStore {
464
+ private readonly queues;
465
+ private key;
466
+ add(roomId: string, threadKey: string | undefined, item: PendingMediaItem): void;
467
+ drain(roomId: string, threadKey: string | undefined, sender: string): PendingMediaItem[];
468
+ }
469
+
470
+ export { type AgentBinding, BotPool, type CreateMatrixTransportOptions, type EnsureDefaultChannelOpts, type EnsureSpaceOpts, INLINE_IMAGE_MIMES, MAX_DOWNLOAD_BYTES, MAX_INLINE_IMAGE_BYTES, MAX_MEDIA_PER_TURN, MEDIA_MSGTYPES, MatrixClient, type MatrixClientOptions, MatrixContextProvider, type MatrixContextProviderOpts, type MatrixTransportConfig, type MaybeMessage, MediaClient, type MediaClientLike, type MediaClientOptions, type PendingMediaItem, PendingMediaStore, type PublishOpts, type PublisherHandle, type RouteMatch, type SendCustomEventInput, type SendMessageInput, type StartOpts as StartWorkforcePublisherOpts, type WorkforceRoster, type WriteAttachmentInput, buildWorkforceRoster, createMatrixTransport, ensureDefaultChannel, ensureWorkforceSpace, extractMentions, isMediaMsgtype, parseMxcUri, publishWorkforce, renderRegistration, route, serverNameFromMxid, startWorkforcePublisher, writeAttachment };
package/dist/index.js CHANGED
@@ -359,14 +359,29 @@ var MatrixContextProvider = class {
359
359
  }
360
360
  toMessage(ev) {
361
361
  if (ev.type !== "m.room.message") return null;
362
- if (ev.content?.msgtype !== "m.text" || typeof ev.content.body !== "string") return null;
362
+ const msgtype = ev.content?.msgtype;
363
+ const body = ev.content?.body;
363
364
  const agent = this.opts.agentBots.get(ev.sender);
364
- const relatesTo = ev.content["m.relates_to"];
365
+ const relatesTo = ev.content?.["m.relates_to"];
365
366
  const threadId = relatesTo?.rel_type === "m.thread" && relatesTo.event_id ? relatesTo.event_id : void 0;
367
+ if (msgtype === "m.image" || msgtype === "m.file" || msgtype === "m.video" || msgtype === "m.audio") {
368
+ const kind = msgtype.slice(2);
369
+ const name = typeof body === "string" && body ? body : "untitled";
370
+ return {
371
+ id: ev.event_id,
372
+ sender: ev.sender,
373
+ text: `[${kind}: ${name}]`,
374
+ timestamp: new Date(ev.origin_server_ts).toISOString(),
375
+ is_agent: agent !== void 0,
376
+ ...agent !== void 0 ? { agent_name: agent } : {},
377
+ ...threadId !== void 0 ? { thread_id: threadId } : {}
378
+ };
379
+ }
380
+ if (msgtype !== "m.text" || typeof body !== "string") return null;
366
381
  return {
367
382
  id: ev.event_id,
368
383
  sender: ev.sender,
369
- text: ev.content.body,
384
+ text: body,
370
385
  timestamp: new Date(ev.origin_server_ts).toISOString(),
371
386
  is_agent: agent !== void 0,
372
387
  ...agent !== void 0 ? { agent_name: agent } : {},
@@ -440,6 +455,10 @@ function stripMention(body, userId) {
440
455
  }
441
456
 
442
457
  // src/router.ts
458
+ var MEDIA_MSGTYPES = /* @__PURE__ */ new Set(["m.image", "m.file", "m.video", "m.audio"]);
459
+ function isMediaMsgtype(t) {
460
+ return t !== void 0 && MEDIA_MSGTYPES.has(t);
461
+ }
443
462
  function inboundThreadRoot(event) {
444
463
  const r = event.content?.["m.relates_to"];
445
464
  return r?.rel_type === "m.thread" && r.event_id ? r.event_id : void 0;
@@ -447,6 +466,7 @@ function inboundThreadRoot(event) {
447
466
  function route(event, agents, threadStates) {
448
467
  if (event.type !== "m.room.message") return [];
449
468
  if (!event.content?.msgtype) return [];
469
+ if (isMediaMsgtype(event.content.msgtype)) return [];
450
470
  const mentions = new Set(extractMentions(event));
451
471
  const matches = [];
452
472
  const threadRoot = inboundThreadRoot(event);
@@ -485,10 +505,12 @@ async function ensureWorkforceSpace(opts) {
485
505
  name: display,
486
506
  preset: opts.preset,
487
507
  creation_content: { type: "m.space" },
488
- // A workspace is joined by invitation, not self-service. Pin the space's
489
- // join rule to invite regardless of preset so it can't be walked into
490
- // (which would otherwise satisfy every restricted child room's allow).
491
- initial_state: [{ type: "m.room.join_rules", state_key: "", content: { join_rule: "invite" } }]
508
+ // Pin the join rule regardless of preset. Defaults to invite so the space
509
+ // can't be walked into (which would otherwise satisfy every restricted
510
+ // child room's allow); `zooid dev` opts into `public` for local-only use.
511
+ initial_state: [
512
+ { type: "m.room.join_rules", state_key: "", content: { join_rule: opts.joinRule ?? "invite" } }
513
+ ]
492
514
  };
493
515
  if (opts.admins && opts.admins.length > 0) {
494
516
  body.invite = opts.admins;
@@ -804,8 +826,180 @@ function toMatrixHtml(markdown) {
804
826
  });
805
827
  }
806
828
 
829
+ // src/pending-media.ts
830
+ var MAX_MEDIA_PER_TURN = 8;
831
+ var PendingMediaStore = class {
832
+ queues = /* @__PURE__ */ new Map();
833
+ key(roomId, threadKey) {
834
+ return `${roomId} ${threadKey ?? "main"}`;
835
+ }
836
+ add(roomId, threadKey, item) {
837
+ const k = this.key(roomId, threadKey);
838
+ const q = this.queues.get(k) ?? [];
839
+ q.push(item);
840
+ while (q.length > MAX_MEDIA_PER_TURN) q.shift();
841
+ this.queues.set(k, q);
842
+ }
843
+ drain(roomId, threadKey, sender) {
844
+ const k = this.key(roomId, threadKey);
845
+ const q = this.queues.get(k) ?? [];
846
+ const mine = q.filter((i) => i.sender === sender);
847
+ const rest = q.filter((i) => i.sender !== sender);
848
+ if (rest.length) this.queues.set(k, rest);
849
+ else this.queues.delete(k);
850
+ return mine;
851
+ }
852
+ };
853
+
854
+ // src/media-client.ts
855
+ var MAX_INLINE_IMAGE_BYTES = 524288;
856
+ var INLINE_IMAGE_MIMES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
857
+ var MAX_DOWNLOAD_BYTES = 33554432;
858
+ function parseMxcUri(uri) {
859
+ const m = /^mxc:\/\/([^/]+)\/(.+)$/.exec(uri);
860
+ return m ? { serverName: m[1], mediaId: m[2] } : null;
861
+ }
862
+ var MediaClient = class {
863
+ homeserver;
864
+ asToken;
865
+ fetch;
866
+ constructor(opts) {
867
+ this.homeserver = opts.homeserver.replace(/\/$/, "");
868
+ this.asToken = opts.asToken;
869
+ this.fetch = opts.fetch ?? globalThis.fetch;
870
+ }
871
+ async download(input) {
872
+ const parsed = parseMxcUri(input.mxcUri);
873
+ if (!parsed) throw new Error(`not an mxc uri: ${input.mxcUri}`);
874
+ const url = `${this.homeserver}/_matrix/client/v1/media/download/${encodeURIComponent(parsed.serverName)}/${encodeURIComponent(parsed.mediaId)}?user_id=${encodeURIComponent(input.asUserId)}`;
875
+ const r = await this.fetch(url, {
876
+ headers: { Authorization: `Bearer ${this.asToken}` }
877
+ });
878
+ if (!r.ok) throw new Error(`media download failed: ${r.status}`);
879
+ const buf = new Uint8Array(await r.arrayBuffer());
880
+ const max = input.maxBytes ?? MAX_DOWNLOAD_BYTES;
881
+ if (buf.byteLength > max) {
882
+ throw new Error(`media too large: ${buf.byteLength} > ${max}`);
883
+ }
884
+ return { data: buf, contentType: r.headers.get("content-type") ?? "application/octet-stream" };
885
+ }
886
+ async upload(input) {
887
+ const params = new URLSearchParams();
888
+ if (input.filename) params.set("filename", input.filename);
889
+ params.set("user_id", input.asUserId);
890
+ const r = await this.fetch(`${this.homeserver}/_matrix/media/v3/upload?${params}`, {
891
+ method: "POST",
892
+ headers: { Authorization: `Bearer ${this.asToken}`, "Content-Type": input.contentType },
893
+ body: input.data
894
+ });
895
+ if (!r.ok) throw new Error(`media upload failed: ${r.status}`);
896
+ return await r.json();
897
+ }
898
+ };
899
+
900
+ // src/attachments.ts
901
+ import { mkdirSync, writeFileSync } from "fs";
902
+ import { join } from "path";
903
+ import { posix } from "path";
904
+ function sanitize(s, fallback) {
905
+ const cleaned = s.replace(/[^A-Za-z0-9._-]/g, "").replace(/^\.+/, "");
906
+ return cleaned || fallback;
907
+ }
908
+ function writeAttachment(input) {
909
+ const dir = sanitize(input.eventId, "event");
910
+ const name = sanitize(input.filename, "file");
911
+ const hostDir = join(input.workspaceDir, ".zooid", "attachments", dir);
912
+ mkdirSync(hostDir, { recursive: true });
913
+ const hostPath = join(hostDir, name);
914
+ writeFileSync(hostPath, input.data);
915
+ const agentPath = posix.join(input.agentWorkspacePath, ".zooid", "attachments", dir, name);
916
+ return { hostPath, agentPath };
917
+ }
918
+
807
919
  // src/transport.ts
808
920
  var STARTUP_GRACE_MS = 5e3;
921
+ async function buildMediaBlocks(items, opts) {
922
+ const blocks = [];
923
+ const pathLines = [];
924
+ if (!opts.media || items.length === 0) return { blocks, pathLines };
925
+ for (const item of items) {
926
+ try {
927
+ const isInlineCandidate = item.msgtype === "m.image" && INLINE_IMAGE_MIMES.includes(item.info?.mimetype ?? "") && (item.info?.size === void 0 || item.info.size <= MAX_INLINE_IMAGE_BYTES);
928
+ if (isInlineCandidate) {
929
+ const { data, contentType } = await opts.media.download({
930
+ mxcUri: item.url,
931
+ asUserId: opts.agent.userId
932
+ });
933
+ if (data.byteLength <= MAX_INLINE_IMAGE_BYTES) {
934
+ blocks.push({
935
+ type: "image",
936
+ data: Buffer.from(data).toString("base64"),
937
+ mimeType: contentType
938
+ });
939
+ continue;
940
+ }
941
+ if (opts.agent.workspaceDir) {
942
+ const paths = opts.writeAttachmentFn({
943
+ workspaceDir: opts.agent.workspaceDir,
944
+ agentWorkspacePath: opts.agent.agentWorkspacePath ?? opts.agent.workspaceDir,
945
+ eventId: item.eventId,
946
+ filename: item.filename ?? item.body,
947
+ data
948
+ });
949
+ blocks.push({
950
+ type: "resource_link",
951
+ uri: `file://${paths.agentPath}`,
952
+ name: item.filename ?? item.body
953
+ });
954
+ pathLines.push(`Attached file: ${paths.agentPath}`);
955
+ }
956
+ } else {
957
+ if (!opts.agent.workspaceDir) continue;
958
+ const { data } = await opts.media.download({
959
+ mxcUri: item.url,
960
+ asUserId: opts.agent.userId
961
+ });
962
+ const paths = opts.writeAttachmentFn({
963
+ workspaceDir: opts.agent.workspaceDir,
964
+ agentWorkspacePath: opts.agent.agentWorkspacePath ?? opts.agent.workspaceDir,
965
+ eventId: item.eventId,
966
+ filename: item.filename ?? item.body,
967
+ data
968
+ });
969
+ blocks.push({
970
+ type: "resource_link",
971
+ uri: `file://${paths.agentPath}`,
972
+ name: item.filename ?? item.body,
973
+ mimeType: item.info?.mimetype,
974
+ size: item.info?.size
975
+ });
976
+ pathLines.push(`Attached file: ${paths.agentPath}`);
977
+ }
978
+ } catch (err) {
979
+ opts.onError(item, err);
980
+ }
981
+ }
982
+ return { blocks, pathLines };
983
+ }
984
+ async function sendMediaError(ctx, _err, message, client) {
985
+ await client.sendCustomEvent({
986
+ roomId: ctx.roomId,
987
+ asUserId: ctx.agent.userId,
988
+ eventType: "eco.zoon.error",
989
+ content: toErrorBody(
990
+ {
991
+ kind: "error",
992
+ agentId: ctx.agent.name,
993
+ sessionId: null,
994
+ turnId: null,
995
+ code: "media_failed",
996
+ message: message.slice(0, 250),
997
+ transient: false
998
+ },
999
+ ctx.threadRoot
1000
+ )
1001
+ }).catch((e) => console.warn(`[matrix:${ctx.agent.name}] eco.zoon.error send failed:`, e));
1002
+ }
809
1003
  var SEEN_EVENT_CAP = 5e3;
810
1004
  var DRAIN_QUIET_MS = 300;
811
1005
  var DRAIN_MAX_MS = 3e4;
@@ -818,6 +1012,9 @@ function createMatrixTransport(opts) {
818
1012
  const { agents, approvals, client, bindings, hsToken, adminUserId } = opts;
819
1013
  const drainQuietMs = opts.drainQuietMs ?? DRAIN_QUIET_MS;
820
1014
  const drainMaxMs = opts.drainMaxMs ?? DRAIN_MAX_MS;
1015
+ const mediaClient = opts.media;
1016
+ const writeAttachmentFn = opts.writeAttachmentFn ?? writeAttachment;
1017
+ const pendingMedia = new PendingMediaStore();
821
1018
  const pool = new BotPool(client, bindings);
822
1019
  const sessions = /* @__PURE__ */ new Map();
823
1020
  const buffers = /* @__PURE__ */ new Map();
@@ -843,6 +1040,29 @@ function createMatrixTransport(opts) {
843
1040
  buffers.set(event.sessionId, current + prefix + block.text);
844
1041
  if (event.messageId !== void 0)
845
1042
  bufferMessageIds.set(event.sessionId, event.messageId);
1043
+ } else if (block.type === "image" && typeof block.data === "string" && typeof block.mimeType === "string" && mediaClient) {
1044
+ const ctx2 = sessions.get(event.sessionId);
1045
+ if (ctx2) {
1046
+ const bytes = Buffer.from(block.data, "base64");
1047
+ const ext = (block.mimeType.split("/")[1] ?? "png").replace(/[^a-z0-9]/gi, "");
1048
+ const filename = `image.${ext}`;
1049
+ void mediaClient.upload({ data: bytes, contentType: block.mimeType, filename, asUserId: ctx2.agent.userId }).then(
1050
+ ({ content_uri }) => client.sendMessage({
1051
+ roomId: ctx2.roomId,
1052
+ asUserId: ctx2.agent.userId,
1053
+ threadRoot: ctx2.threadRoot,
1054
+ content: {
1055
+ msgtype: "m.image",
1056
+ body: filename,
1057
+ url: content_uri,
1058
+ info: { mimetype: block.mimeType, size: bytes.length }
1059
+ }
1060
+ })
1061
+ ).catch((err) => {
1062
+ console.warn(`[matrix:${name}] outbound image upload failed:`, err);
1063
+ void sendMediaError(ctx2, err, "agent image upload failed", client);
1064
+ });
1065
+ }
846
1066
  } else {
847
1067
  console.warn(`[matrix:${name}] dropped chunk block type=${block.type}`, block);
848
1068
  }
@@ -985,6 +1205,18 @@ function createMatrixTransport(opts) {
985
1205
  continue;
986
1206
  }
987
1207
  logInbound(evt);
1208
+ if (evt.type === "m.room.message" && isMediaMsgtype(evt.content?.msgtype) && evt.room_id && evt.event_id && evt.sender && evt.content?.url && !bindings.some((b) => b.userId === evt.sender)) {
1209
+ pendingMedia.add(evt.room_id, inboundThreadRoot2(evt), {
1210
+ eventId: evt.event_id,
1211
+ sender: evt.sender,
1212
+ msgtype: evt.content.msgtype,
1213
+ body: evt.content.body ?? "",
1214
+ filename: evt.content.filename,
1215
+ url: evt.content.url,
1216
+ info: evt.content.info
1217
+ });
1218
+ continue;
1219
+ }
988
1220
  const promotedRoot = inboundThreadRoot2(evt) ?? evt.event_id;
989
1221
  const inboundRel = inboundThreadRoot2(evt);
990
1222
  if (evt.type === "m.room.message" && inboundRel && !threadStates.has(inboundRel) && evt.room_id) {
@@ -1101,10 +1333,30 @@ function createMatrixTransport(opts) {
1101
1333
  try {
1102
1334
  const rawBody = evt.content?.body ?? "";
1103
1335
  const promptText = stripMention(rawBody, agent.userId);
1336
+ const pendingItems = pendingMedia.drain(
1337
+ evt.room_id,
1338
+ inboundThreadRoot2(evt),
1339
+ evt.sender ?? ""
1340
+ );
1341
+ const { blocks, pathLines } = await buildMediaBlocks(pendingItems, {
1342
+ agent,
1343
+ media: mediaClient,
1344
+ writeAttachmentFn,
1345
+ onError: (item, err) => {
1346
+ console.warn(`[matrix:${agent.name}] media_failed for ${item.body}:`, err);
1347
+ void sendMediaError(
1348
+ { agent, roomId: evt.room_id, threadRoot },
1349
+ err,
1350
+ `Could not process attachment: ${item.body}`,
1351
+ client
1352
+ );
1353
+ }
1354
+ });
1355
+ const fullPromptText = [promptText, ...pathLines].filter(Boolean).join("\n");
1104
1356
  await agents.prompt(agent.name, {
1105
1357
  threadId: sessionKey,
1106
1358
  channelId: evt.room_id,
1107
- content: [{ type: "text", text: promptText }]
1359
+ content: [...blocks, { type: "text", text: fullPromptText }]
1108
1360
  });
1109
1361
  const drainStart = Date.now();
1110
1362
  let drained = buffers.get(sessionId) ?? "";
@@ -1249,17 +1501,27 @@ async function startWorkforcePublisher(opts) {
1249
1501
  }
1250
1502
  export {
1251
1503
  BotPool,
1504
+ INLINE_IMAGE_MIMES,
1505
+ MAX_DOWNLOAD_BYTES,
1506
+ MAX_INLINE_IMAGE_BYTES,
1507
+ MAX_MEDIA_PER_TURN,
1508
+ MEDIA_MSGTYPES,
1252
1509
  MatrixClient,
1253
1510
  MatrixContextProvider,
1511
+ MediaClient,
1512
+ PendingMediaStore,
1254
1513
  buildWorkforceRoster,
1255
1514
  createMatrixTransport,
1256
1515
  ensureDefaultChannel,
1257
1516
  ensureWorkforceSpace,
1258
1517
  extractMentions,
1518
+ isMediaMsgtype,
1519
+ parseMxcUri,
1259
1520
  publishWorkforce,
1260
1521
  renderRegistration,
1261
1522
  route,
1262
1523
  serverNameFromMxid,
1263
- startWorkforcePublisher
1524
+ startWorkforcePublisher,
1525
+ writeAttachment
1264
1526
  };
1265
1527
  //# sourceMappingURL=index.js.map