@verentis/sdk 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -295,6 +295,37 @@ contracts must not assume a locally present feature-branch API is already deploy
295
295
  | `BridgeAuth` | Auth strategy that receives tokens via the bridge |
296
296
  | `matchMimeType` / `findBestMatch` | MIME type matching utilities |
297
297
 
298
+ ### Chat
299
+
300
+ `client.chat` is the documented workspace chat API (`/v1/chat`). The caller and workspace always come from the
301
+ token; message content is untrusted Markdown that readers render without raw HTML.
302
+
303
+ ```ts
304
+ const conversation = await client.chat.openDirect({ kind: 'user', id: colleagueId })
305
+ await client.chat.sendText(conversation.id, 'Hi **there**', { idempotencyKey: crypto.randomUUID() })
306
+
307
+ // Inside the workspace (needs the approved `chat.read` permission): identifier-only live hints over the
308
+ // host's realtime connection. Hints can be lost or repeated, so catch up from durable events.
309
+ const stop = client.chat.subscribeChanges({
310
+ onChange: async (hint) => {
311
+ const page = await client.chat.events(hint.conversationId, { afterSeq: lastAppliedSeq })
312
+ // apply page.items; if page.resetRequired, reload the conversation and its history
313
+ },
314
+ onConnection: (status) => { if (status === 'connected') void reconcile() },
315
+ })
316
+
317
+ // From an installed app's script: post as the app's bot (declared in `spec.chat.bots`). A command script receives
318
+ // chat_conversation_id and chat_invocation_key with its arguments; reusing the key makes a retried run reply once.
319
+ await client.chat.postAsBot({
320
+ conversationId: params.chat_conversation_id,
321
+ parts: [{ type: 'text', data: { markdown: 'Invoice 42 needs approval' } }],
322
+ idempotencyKey: params.chat_invocation_key,
323
+ })
324
+
325
+ // Run an installed app's slash command as the current user.
326
+ await client.chat.runCommand(conversationId, 'invoicing/approve-invoice', { invoice: 'INV-42' })
327
+ ```
328
+
298
329
  ### Workspace settings
299
330
 
300
331
  Apps whose manifest declares `spec.settings[]` get workspace setting definitions auto-registered
package/dist/index.cjs CHANGED
@@ -292,7 +292,8 @@ var ExecutionsModule = class {
292
292
  tool: options?.tool,
293
293
  mode: options?.mode,
294
294
  arguments: options?.arguments ?? [],
295
- parameters: serializeParameters(options?.parameters)
295
+ parameters: serializeParameters(options?.parameters),
296
+ ...options?.invocationKey ? { invocationKey: options.invocationKey } : {}
296
297
  }),
297
298
  headers: { "Content-Type": "application/json" }
298
299
  });
@@ -338,9 +339,9 @@ var ExecutionsModule = class {
338
339
  return descriptor;
339
340
  }
340
341
  /** Get an execution by id. */
341
- async get(id, options) {
342
+ async get(id2, options) {
342
343
  const workspaceId = this.requireWorkspaceId(options?.workspaceId);
343
- return this.http.request("GET", `/executions/${encodeURIComponent(id)}`, {
344
+ return this.http.request("GET", `/executions/${encodeURIComponent(id2)}`, {
344
345
  params: { workspaceId }
345
346
  });
346
347
  }
@@ -365,17 +366,17 @@ var ExecutionsModule = class {
365
366
  });
366
367
  }
367
368
  /** Get the captured stdout/stderr of an execution. */
368
- async logs(id, options) {
369
+ async logs(id2, options) {
369
370
  const workspaceId = this.requireWorkspaceId(options?.workspaceId);
370
- return this.http.request("GET", `/executions/${encodeURIComponent(id)}/logs`, {
371
+ return this.http.request("GET", `/executions/${encodeURIComponent(id2)}/logs`, {
371
372
  params: { workspaceId }
372
373
  });
373
374
  }
374
375
  /** Cancel a pending or running execution. */
375
- async cancel(id, options) {
376
+ async cancel(id2, options) {
376
377
  const workspaceId = this.requireWorkspaceId(options?.workspaceId);
377
- return this.http.request("POST", `/executions/${encodeURIComponent(id)}/cancel`, {
378
- body: JSON.stringify({ id, workspaceId }),
378
+ return this.http.request("POST", `/executions/${encodeURIComponent(id2)}/cancel`, {
379
+ body: JSON.stringify({ id: id2, workspaceId }),
379
380
  headers: { "Content-Type": "application/json" }
380
381
  });
381
382
  }
@@ -383,16 +384,16 @@ var ExecutionsModule = class {
383
384
  * Poll an execution until it reaches a terminal status. Returns immediately if it is already
384
385
  * terminal. Throws if `timeoutMs` elapses or the supplied signal aborts.
385
386
  */
386
- async wait(id, options) {
387
+ async wait(id2, options) {
387
388
  const interval = options?.intervalMs ?? 1e3;
388
389
  const deadline = options?.timeoutMs ? Date.now() + options.timeoutMs : void 0;
389
390
  for (; ; ) {
390
- const execution = await this.get(id, { workspaceId: options?.workspaceId });
391
+ const execution = await this.get(id2, { workspaceId: options?.workspaceId });
391
392
  if (isTerminalStatus(execution.status)) {
392
393
  return execution;
393
394
  }
394
395
  if (deadline !== void 0 && Date.now() >= deadline) {
395
- throw new Error(`Timed out waiting for execution '${id}' to complete`);
396
+ throw new Error(`Timed out waiting for execution '${id2}' to complete`);
396
397
  }
397
398
  await delay(interval, options?.signal);
398
399
  }
@@ -409,11 +410,11 @@ var ExecutionsModule = class {
409
410
  return this.wait(execution.id, options);
410
411
  }
411
412
  requireWorkspaceId(override) {
412
- const id = override ?? this.workspaceId;
413
- if (!id) {
413
+ const id2 = override ?? this.workspaceId;
414
+ if (!id2) {
414
415
  throw new Error("workspaceId is required: provide it in options or when creating the client");
415
416
  }
416
- return id;
417
+ return id2;
417
418
  }
418
419
  };
419
420
  function serializeParameters(parameters) {
@@ -454,8 +455,8 @@ var EnginesModule = class {
454
455
  }
455
456
  });
456
457
  }
457
- async get(id, options) {
458
- return this.http.request("GET", `/engines/${encodeURIComponent(id)}`, {
458
+ async get(id2, options) {
459
+ return this.http.request("GET", `/engines/${encodeURIComponent(id2)}`, {
459
460
  params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
460
461
  });
461
462
  }
@@ -676,11 +677,11 @@ var FilesModule = class {
676
677
  });
677
678
  }
678
679
  workspaceHeaders() {
679
- const headers = {};
680
+ const headers2 = {};
680
681
  if (this.workspaceId) {
681
- headers["X-Workspace-Id"] = this.workspaceId;
682
+ headers2["X-Workspace-Id"] = this.workspaceId;
682
683
  }
683
- return headers;
684
+ return headers2;
684
685
  }
685
686
  };
686
687
  function requireNonBlank(value, name) {
@@ -772,14 +773,14 @@ var InstallationsModule = class {
772
773
  }
773
774
  });
774
775
  }
775
- async update(id, options) {
776
+ async update(id2, options) {
776
777
  const workspaceId = this.requireWorkspaceId(options?.workspaceId);
777
778
  return this.http.request(
778
779
  "PUT",
779
- `/installations/${encodeURIComponent(id)}`,
780
+ `/installations/${encodeURIComponent(id2)}`,
780
781
  {
781
782
  body: JSON.stringify({
782
- id,
783
+ id: id2,
783
784
  workspaceId,
784
785
  packageVersion: options?.packageVersion,
785
786
  installMode: options?.installMode
@@ -788,8 +789,8 @@ var InstallationsModule = class {
788
789
  }
789
790
  );
790
791
  }
791
- async uninstall(id, options) {
792
- await this.http.request("DELETE", `/installations/${encodeURIComponent(id)}`, {
792
+ async uninstall(id2, options) {
793
+ await this.http.request("DELETE", `/installations/${encodeURIComponent(id2)}`, {
793
794
  params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
794
795
  });
795
796
  }
@@ -815,10 +816,10 @@ var ResourcesModule = class {
815
816
  params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
816
817
  });
817
818
  }
818
- async get(id, options) {
819
+ async get(id2, options) {
819
820
  return this.http.request(
820
821
  "GET",
821
- `/resources/${encodeURIComponent(id)}`,
822
+ `/resources/${encodeURIComponent(id2)}`,
822
823
  { params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) } }
823
824
  );
824
825
  }
@@ -982,10 +983,10 @@ var NotificationsModule = class {
982
983
  }
983
984
  }
984
985
  }
985
- async request(id, options) {
986
+ async request(id2, options) {
986
987
  return this.http.request(
987
988
  "GET",
988
- `/notifications/requests/${encodeURIComponent(id)}`,
989
+ `/notifications/requests/${encodeURIComponent(id2)}`,
989
990
  {
990
991
  params: {
991
992
  workspaceId: this.requireWorkspaceId(options?.workspaceId),
@@ -1029,8 +1030,8 @@ var BusinessModule = class _BusinessModule {
1029
1030
  body: JSON.stringify(command)
1030
1031
  });
1031
1032
  }
1032
- async read(id) {
1033
- return this.http.request("GET", `/business/records/${encodeURIComponent(id)}`, { headers: this.headers() });
1033
+ async read(id2) {
1034
+ return this.http.request("GET", `/business/records/${encodeURIComponent(id2)}`, { headers: this.headers() });
1034
1035
  }
1035
1036
  async list(kind) {
1036
1037
  return this.http.request("GET", "/business/records", {
@@ -1072,6 +1073,190 @@ var BusinessModule = class _BusinessModule {
1072
1073
  }
1073
1074
  };
1074
1075
 
1076
+ // src/modules/chat.ts
1077
+ var JSON_HEADERS = { "Content-Type": "application/json" };
1078
+ var ChatModule = class {
1079
+ constructor(http, bridge = null, executions) {
1080
+ this.http = http;
1081
+ this.bridge = bridge;
1082
+ this.executions = executions;
1083
+ }
1084
+ http;
1085
+ bridge;
1086
+ executions;
1087
+ // ── Conversations ─────────────────────────────────────────────────
1088
+ listConversations(options) {
1089
+ return this.http.request("GET", "/chat/conversations", { params: page(options) });
1090
+ }
1091
+ createConversation(options) {
1092
+ const { idempotencyKey, ...body } = options;
1093
+ return this.http.request("POST", "/chat/conversations", { body: JSON.stringify(body), headers: headers(idempotencyKey) });
1094
+ }
1095
+ /** Opens (or returns the existing) direct conversation with one participant. */
1096
+ openDirect(participant) {
1097
+ return this.createConversation({ kind: "direct", participants: [participant] });
1098
+ }
1099
+ getConversation(conversationId) {
1100
+ return this.http.request("GET", `/chat/conversations/${id(conversationId)}`);
1101
+ }
1102
+ updateConversation(conversationId, changes) {
1103
+ return this.http.request("PATCH", `/chat/conversations/${id(conversationId)}`, { body: JSON.stringify(changes), headers: JSON_HEADERS });
1104
+ }
1105
+ join(conversationId) {
1106
+ return this.http.request("POST", `/chat/conversations/${id(conversationId)}/join`, { body: "{}", headers: JSON_HEADERS });
1107
+ }
1108
+ listPublicChannels(options) {
1109
+ return this.http.request("GET", "/chat/channels", { params: { ...page(options), query: options?.query } });
1110
+ }
1111
+ // ── Members ───────────────────────────────────────────────────────
1112
+ listMembers(conversationId) {
1113
+ return this.http.request("GET", `/chat/conversations/${id(conversationId)}/members`);
1114
+ }
1115
+ addMembers(conversationId, participants, role) {
1116
+ return this.http.request("POST", `/chat/conversations/${id(conversationId)}/members`, {
1117
+ body: JSON.stringify({ participants, role }),
1118
+ headers: JSON_HEADERS
1119
+ });
1120
+ }
1121
+ /** Removes a member; pass yourself to leave. */
1122
+ async removeMember(conversationId, participant) {
1123
+ await this.http.request("DELETE", `/chat/conversations/${id(conversationId)}/members/${participant.kind}/${id(participant.id)}`);
1124
+ }
1125
+ changeRole(conversationId, participant, role) {
1126
+ return this.http.request("PUT", `/chat/conversations/${id(conversationId)}/members/${participant.kind}/${id(participant.id)}/role`, {
1127
+ body: JSON.stringify({ role }),
1128
+ headers: JSON_HEADERS
1129
+ });
1130
+ }
1131
+ // ── Messages ──────────────────────────────────────────────────────
1132
+ listMessages(conversationId, options) {
1133
+ return this.http.request("GET", `/chat/conversations/${id(conversationId)}/messages`, {
1134
+ params: { before: options?.before, limit: options?.limit }
1135
+ });
1136
+ }
1137
+ getMessage(conversationId, messageId) {
1138
+ return this.http.request("GET", `/chat/conversations/${id(conversationId)}/messages/${id(messageId)}`);
1139
+ }
1140
+ send(conversationId, parts, options) {
1141
+ return this.http.request("POST", `/chat/conversations/${id(conversationId)}/messages`, {
1142
+ body: JSON.stringify({ parts, replyTo: options?.replyTo }),
1143
+ headers: headers(options?.idempotencyKey)
1144
+ });
1145
+ }
1146
+ /** Sends Markdown text (rendered without raw HTML by every reader). */
1147
+ sendText(conversationId, markdown, options) {
1148
+ return this.send(conversationId, [{ type: "text", data: { markdown } }], options);
1149
+ }
1150
+ /** Edits your own message; pass the revision you saw to reject a concurrent edit. */
1151
+ edit(conversationId, messageId, parts, options) {
1152
+ return this.http.request("PATCH", `/chat/conversations/${id(conversationId)}/messages/${id(messageId)}`, {
1153
+ body: JSON.stringify({ parts }),
1154
+ headers: { ...JSON_HEADERS, ...options?.revision ? { "If-Match": `"${options.revision}"` } : {} }
1155
+ });
1156
+ }
1157
+ delete(conversationId, messageId) {
1158
+ return this.http.request("DELETE", `/chat/conversations/${id(conversationId)}/messages/${id(messageId)}`);
1159
+ }
1160
+ /**
1161
+ * Durable changes after a position: `afterSeq` is a snapshot's `highWatermark` (or the last sequence you applied);
1162
+ * `after` is the `next` cursor of a previous page.
1163
+ */
1164
+ events(conversationId, options) {
1165
+ return this.http.request("GET", `/chat/conversations/${id(conversationId)}/events`, {
1166
+ params: { after: options?.after, afterSeq: options?.afterSeq, limit: options?.limit }
1167
+ });
1168
+ }
1169
+ // ── Read state and preferences ────────────────────────────────────
1170
+ /** Marks messages read through a sequence; never moves back and never past what the server has. */
1171
+ markRead(conversationId, readThroughSeq) {
1172
+ return this.http.request("PUT", `/chat/conversations/${id(conversationId)}/read-state`, {
1173
+ body: JSON.stringify({ readThroughSeq }),
1174
+ headers: JSON_HEADERS
1175
+ });
1176
+ }
1177
+ setPreferences(conversationId, preferences) {
1178
+ return this.http.request("PUT", `/chat/conversations/${id(conversationId)}/preferences`, {
1179
+ body: JSON.stringify(preferences),
1180
+ headers: JSON_HEADERS
1181
+ });
1182
+ }
1183
+ // ── People, bots and commands ─────────────────────────────────────
1184
+ /** People in your workspace (name and avatar only). */
1185
+ searchPeople(options) {
1186
+ return this.http.request("GET", "/chat/people", { params: { ...page(options), query: options?.query } });
1187
+ }
1188
+ listBots() {
1189
+ return this.http.request("GET", "/chat/bots");
1190
+ }
1191
+ /** Workspace managers (`chat.manage`) decide whether a bot may message people directly. */
1192
+ updateBotSettings(botId, settings) {
1193
+ return this.http.request("PUT", `/chat/bots/${id(botId)}/settings`, { body: JSON.stringify(settings), headers: JSON_HEADERS });
1194
+ }
1195
+ listCommands() {
1196
+ return this.http.request("GET", "/chat/commands");
1197
+ }
1198
+ /** Validates and records a command invocation, returning the Execution request to run as you. */
1199
+ invokeCommand(conversationId, command, args, options) {
1200
+ return this.http.request("POST", `/chat/conversations/${id(conversationId)}/commands`, {
1201
+ body: JSON.stringify({ command, arguments: args ?? {} }),
1202
+ headers: headers(options?.idempotencyKey)
1203
+ });
1204
+ }
1205
+ /**
1206
+ * Invokes an installed app's command and runs its script with your own authorisation. Retrying with the same
1207
+ * idempotency key reuses the invocation key, so Execution never runs it twice.
1208
+ */
1209
+ async runCommand(conversationId, command, args, options) {
1210
+ if (!this.executions) throw new Error("Running commands needs the executions module.");
1211
+ const invocation = await this.invokeCommand(conversationId, command, args, options);
1212
+ const execution = await this.executions.run(invocation.filePath, {
1213
+ workspaceId: invocation.workspaceId,
1214
+ branch: invocation.branch,
1215
+ parameters: JSON.parse(invocation.parameters),
1216
+ invocationKey: invocation.invocationKey
1217
+ });
1218
+ return { invocation, execution };
1219
+ }
1220
+ /**
1221
+ * Posts as the installed app's bot from a running script. The bot is derived from the script's execution; the
1222
+ * conversation must have added the bot (or, for `userId`, the workspace must allow it to message people).
1223
+ * Retries briefly while Execution's start event is still on its way.
1224
+ */
1225
+ async postAsBot(options) {
1226
+ const { idempotencyKey, ...body } = options;
1227
+ const request = { body: JSON.stringify(body), headers: headers(idempotencyKey ?? globalThis.crypto?.randomUUID?.()) };
1228
+ const deadline = Date.now() + 1e4;
1229
+ for (; ; ) {
1230
+ try {
1231
+ return await this.http.request("POST", "/chat/bot-messages", request);
1232
+ } catch (error) {
1233
+ if (!(error instanceof ApiError) || error.status !== 409 || error.code !== "execution_not_ready") throw error;
1234
+ const delay2 = Math.max(250, error.retryAfterMs ?? 1e3);
1235
+ if (Date.now() + delay2 > deadline) throw error;
1236
+ await new Promise((resolve) => setTimeout(resolve, delay2));
1237
+ }
1238
+ }
1239
+ }
1240
+ // ── Live hints ────────────────────────────────────────────────────
1241
+ /**
1242
+ * Subscribes to live change hints through the host (installed apps only). Returns an unsubscribe function.
1243
+ * Hints carry ids only; duplicates and reordering are normal, and hints are lost while disconnected.
1244
+ */
1245
+ subscribeChanges(handlers) {
1246
+ if (!this.bridge) throw new Error("Live chat hints are only available to apps running inside the workspace.");
1247
+ return this.bridge.subscribeChat(handlers);
1248
+ }
1249
+ };
1250
+ function id(value) {
1251
+ return encodeURIComponent(value);
1252
+ }
1253
+ function page(options) {
1254
+ return { cursor: options?.cursor, limit: options?.limit };
1255
+ }
1256
+ function headers(idempotencyKey) {
1257
+ return idempotencyKey ? { ...JSON_HEADERS, "Idempotency-Key": idempotencyKey } : JSON_HEADERS;
1258
+ }
1259
+
1075
1260
  // src/transport/app-commands.ts
1076
1261
  var APP_COMMAND_BATCH_SIZE = 200;
1077
1262
  var APP_COMMANDS_CAPABILITY = "commands";
@@ -1115,7 +1300,7 @@ function element(value) {
1115
1300
  return record(value) && identifier(value.id) && natural(value.version) && (value.versionNonce === void 0 || natural(value.versionNonce)) && (value.isDeleted === void 0 || typeof value.isDeleted === "boolean");
1116
1301
  }
1117
1302
  function files(value) {
1118
- return record(value) && Object.keys(value).length <= 1e4 && Object.entries(value).every(([id, file]) => record(file) && identifier(id) && file.id === id && typeof file.dataURL === "string" && file.dataURL.length <= 10 * 1024 * 1024 && typeof file.mimeType === "string" && file.mimeType.length > 0 && file.mimeType.length <= 128 && natural(file.created) && (file.lastRetrieved === void 0 || natural(file.lastRetrieved)));
1303
+ return record(value) && Object.keys(value).length <= 1e4 && Object.entries(value).every(([id2, file]) => record(file) && identifier(id2) && file.id === id2 && typeof file.dataURL === "string" && file.dataURL.length <= 10 * 1024 * 1024 && typeof file.mimeType === "string" && file.mimeType.length > 0 && file.mimeType.length <= 128 && natural(file.created) && (file.lastRetrieved === void 0 || natural(file.lastRetrieved)));
1119
1304
  }
1120
1305
  function isNativeSnapshot(value) {
1121
1306
  if (!record(value) || !natural(value.sequence) || !natural(value.savedSequence) || value.savedSequence > value.sequence || typeof value.revision !== "string" || !value.revision || value.revision.length > 1024 || typeof value.readOnly !== "boolean" || !record(value.scene) || !Array.isArray(value.scene.elements) || value.scene.elements.length > 1e4 || !value.scene.elements.every(element) || !files(value.scene.files) || !record(value.scene.appState) || !Array.isArray(value.participants) || value.participants.length > 20)
@@ -1230,6 +1415,7 @@ var Bridge = class {
1230
1415
  commandFlushScheduled = false;
1231
1416
  commandRequests = /* @__PURE__ */ new Set();
1232
1417
  _hostCapabilities = [];
1418
+ chatSubscribers = /* @__PURE__ */ new Set();
1233
1419
  hostMessageHandlers = /* @__PURE__ */ new Map();
1234
1420
  hostMessageRequests = /* @__PURE__ */ new Set();
1235
1421
  wopiRequests = /* @__PURE__ */ new Map();
@@ -1433,13 +1619,13 @@ var Bridge = class {
1433
1619
  }
1434
1620
  };
1435
1621
  }
1436
- queueCommand(id, change) {
1622
+ queueCommand(id2, change) {
1437
1623
  if (change === "upsert") {
1438
- this.commandRemovals.delete(id);
1439
- this.commandUpserts.add(id);
1624
+ this.commandRemovals.delete(id2);
1625
+ this.commandUpserts.add(id2);
1440
1626
  } else {
1441
- this.commandUpserts.delete(id);
1442
- this.commandRemovals.add(id);
1627
+ this.commandUpserts.delete(id2);
1628
+ this.commandRemovals.add(id2);
1443
1629
  }
1444
1630
  this.scheduleCommandFlush();
1445
1631
  }
@@ -1451,14 +1637,14 @@ var Bridge = class {
1451
1637
  this.flushCommands();
1452
1638
  });
1453
1639
  }
1454
- commandSnapshot(id) {
1455
- const command = this.commands.get(id);
1640
+ commandSnapshot(id2) {
1641
+ const command = this.commands.get(id2);
1456
1642
  if (!command) return null;
1457
1643
  return command.busy ? { ...command.descriptor, busy: true } : command.descriptor;
1458
1644
  }
1459
1645
  flushCommands() {
1460
1646
  if (!this.sessionId || !this._hostOrigin || this._destroyed) return;
1461
- const upsert = [...this.commandUpserts].map((id) => this.commandSnapshot(id)).filter((c) => !!c);
1647
+ const upsert = [...this.commandUpserts].map((id2) => this.commandSnapshot(id2)).filter((c) => !!c);
1462
1648
  const remove = this.commandReset ? [] : [...this.commandRemovals];
1463
1649
  let reset = this.commandReset;
1464
1650
  this.commandUpserts.clear();
@@ -1598,6 +1784,41 @@ var Bridge = class {
1598
1784
  this.postToHost({ ...reply, error: "The reply could not be sent to the workspace." });
1599
1785
  }
1600
1786
  }
1787
+ /**
1788
+ * Subscribes to the host's chat live hints (see `ChatModule.subscribeChanges`). The first subscriber asks the host
1789
+ * to relay; the last one to leave stops it. Messages from another session are ignored.
1790
+ */
1791
+ subscribeChat(handlers) {
1792
+ if (this._destroyed) throw new Error("Bridge has been destroyed.");
1793
+ this.chatSubscribers.add(handlers);
1794
+ const subscribe = () => {
1795
+ if (this.sessionId && this._hostOrigin && this.chatSubscribers.size > 0)
1796
+ this.postToHost({ type: "verentis:chat:subscribe", version: 1, sessionId: this.sessionId });
1797
+ };
1798
+ if (this.chatSubscribers.size === 1) {
1799
+ if (this._hostOrigin) subscribe();
1800
+ else void this._initPromise.then(subscribe, () => void 0);
1801
+ }
1802
+ return () => {
1803
+ if (!this.chatSubscribers.delete(handlers) || this.chatSubscribers.size > 0) return;
1804
+ if (!this._destroyed && this.sessionId && this._hostOrigin)
1805
+ this.postToHost({ type: "verentis:chat:unsubscribe", version: 1, sessionId: this.sessionId });
1806
+ };
1807
+ }
1808
+ receiveChat(data) {
1809
+ if (data.type !== "verentis:chat:changed" && data.type !== "verentis:chat:connection" && data.type !== "verentis:chat:subscribed" && data.type !== "verentis:chat:error") return false;
1810
+ if (!this._hostOrigin || data.sessionId !== this.sessionId) return true;
1811
+ for (const subscriber of [...this.chatSubscribers]) {
1812
+ try {
1813
+ if (data.type === "verentis:chat:changed") subscriber.onChange(data.hint);
1814
+ else if (data.type === "verentis:chat:error") subscriber.onError?.(String(data.reason).slice(0, 500));
1815
+ else subscriber.onConnection?.(data.status);
1816
+ } catch (error) {
1817
+ console.error("[Verentis SDK] Chat subscriber failed", error);
1818
+ }
1819
+ }
1820
+ return true;
1821
+ }
1601
1822
  /** Subscribe to a specific message type from the host */
1602
1823
  on(type, listener) {
1603
1824
  if (!this.listeners.has(type)) {
@@ -1622,6 +1843,7 @@ var Bridge = class {
1622
1843
  this.commandRequests.clear();
1623
1844
  this.hostMessageHandlers.clear();
1624
1845
  this.hostMessageRequests.clear();
1846
+ this.chatSubscribers.clear();
1625
1847
  this.listeners.clear();
1626
1848
  for (const pending of this.wopiRequests.values()) {
1627
1849
  clearTimeout(pending.timer);
@@ -1654,7 +1876,7 @@ var Bridge = class {
1654
1876
  if (this.sessionId && this.commands.size > 0) {
1655
1877
  this.commandReset = true;
1656
1878
  this.commandRemovals.clear();
1657
- for (const id of this.commands.keys()) this.commandUpserts.add(id);
1879
+ for (const id2 of this.commands.keys()) this.commandUpserts.add(id2);
1658
1880
  this.scheduleCommandFlush();
1659
1881
  }
1660
1882
  console.info("[Verentis SDK] Received verentis:init from host", {
@@ -1702,6 +1924,7 @@ var Bridge = class {
1702
1924
  void this.receiveHostMessage(data);
1703
1925
  return;
1704
1926
  }
1927
+ if (this.receiveChat(data)) return;
1705
1928
  const typeListeners = this.listeners.get(data.type);
1706
1929
  if (typeListeners) {
1707
1930
  for (const listener of typeListeners) {
@@ -1736,17 +1959,17 @@ var HttpTransport = class {
1736
1959
  async request(method, path, options) {
1737
1960
  const url = this.buildUrl(path, options?.params);
1738
1961
  console.info("[Verentis SDK] Request started", { method, path, url });
1739
- const headers = {};
1962
+ const headers2 = {};
1740
1963
  const authHeader = await this.auth.getAuthHeader();
1741
1964
  if (authHeader) {
1742
- headers["Authorization"] = authHeader;
1965
+ headers2["Authorization"] = authHeader;
1743
1966
  }
1744
1967
  if (options?.headers) {
1745
- Object.assign(headers, options.headers);
1968
+ Object.assign(headers2, options.headers);
1746
1969
  }
1747
1970
  const response = await fetch(url, {
1748
1971
  method,
1749
- headers,
1972
+ headers: headers2,
1750
1973
  body: options?.body
1751
1974
  });
1752
1975
  await this.ensureSuccess(response);
@@ -1765,15 +1988,15 @@ var HttpTransport = class {
1765
1988
  async requestRaw(method, path, options) {
1766
1989
  const url = this.buildUrl(path, options?.params);
1767
1990
  console.info("[Verentis SDK] Raw request started", { method, path, url });
1768
- const headers = {};
1991
+ const headers2 = {};
1769
1992
  const authHeader = await this.auth.getAuthHeader();
1770
1993
  if (authHeader) {
1771
- headers["Authorization"] = authHeader;
1994
+ headers2["Authorization"] = authHeader;
1772
1995
  }
1773
1996
  if (options?.headers) {
1774
- Object.assign(headers, options.headers);
1997
+ Object.assign(headers2, options.headers);
1775
1998
  }
1776
- const response = await fetch(url, { method, headers, body: options?.body });
1999
+ const response = await fetch(url, { method, headers: headers2, body: options?.body });
1777
2000
  console.info("[Verentis SDK] Raw request completed", {
1778
2001
  method,
1779
2002
  path,
@@ -1842,6 +2065,7 @@ var VerentisClient = class {
1842
2065
  settings;
1843
2066
  notifications;
1844
2067
  business;
2068
+ chat;
1845
2069
  context;
1846
2070
  bridge;
1847
2071
  collaboration;
@@ -1878,6 +2102,7 @@ var VerentisClient = class {
1878
2102
  this.settings = new SettingsModule(this._http, options?.workspaceId);
1879
2103
  this.notifications = new NotificationsModule(this._http, options?.workspaceId);
1880
2104
  this.business = new BusinessModule(this._http);
2105
+ this.chat = new ChatModule(this._http, null, this.executions);
1881
2106
  this._ready = Promise.resolve();
1882
2107
  return;
1883
2108
  }
@@ -1928,6 +2153,7 @@ var VerentisClient = class {
1928
2153
  this.settings = new SettingsModule(http, context.workspace.id);
1929
2154
  this.notifications = new NotificationsModule(http, context.workspace.id);
1930
2155
  this.business = new BusinessModule(http);
2156
+ this.chat = new ChatModule(http, this.bridge, this.executions);
1931
2157
  }
1932
2158
  };
1933
2159
  function detectEnvironment() {
@@ -2031,6 +2257,7 @@ exports.ApiKeyAuth = ApiKeyAuth;
2031
2257
  exports.Bridge = Bridge;
2032
2258
  exports.BridgeAuth = BridgeAuth;
2033
2259
  exports.BusinessModule = BusinessModule;
2260
+ exports.ChatModule = ChatModule;
2034
2261
  exports.ContextModule = ContextModule;
2035
2262
  exports.EnginesModule = EnginesModule;
2036
2263
  exports.ExecutionsModule = ExecutionsModule;