@crvouga/mockingbird-service-intercom 1.0.0 → 1.1.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.
@@ -1149,6 +1149,73 @@ var createMetrics = () => {
1149
1149
  };
1150
1150
  };
1151
1151
 
1152
+ // ../core/dist/outbox.js
1153
+ var OutboxStore = class {
1154
+ items;
1155
+ constructor(sqlite, namespace, name = "outbox") {
1156
+ this.items = new Collection(sqlite, namespace, name);
1157
+ }
1158
+ record(item) {
1159
+ this.items.insert(item.id, item);
1160
+ return item;
1161
+ }
1162
+ get(id) {
1163
+ return this.items.get(id);
1164
+ }
1165
+ update(id, item) {
1166
+ this.items.update(id, item);
1167
+ }
1168
+ /** Oldest first, so a suite reads messages in the order they were sent. */
1169
+ list(query = {}) {
1170
+ const to = query.to?.toLowerCase();
1171
+ const matched = this.items.list({ order: "oldest" }).map((row) => row.value).filter((item) => {
1172
+ if (to !== void 0) {
1173
+ const recipients = Array.isArray(item.to) ? item.to : [item.to];
1174
+ if (!recipients.some((r) => r.toLowerCase() === to))
1175
+ return false;
1176
+ }
1177
+ if (query.since !== void 0 && Date.parse(item.createdAt) < query.since)
1178
+ return false;
1179
+ if (query.where && !query.where(item))
1180
+ return false;
1181
+ return true;
1182
+ });
1183
+ return query.limit !== void 0 ? matched.slice(-Math.max(0, query.limit)) : matched;
1184
+ }
1185
+ };
1186
+ var json2 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
1187
+ var parseSince = (value) => {
1188
+ if (value === null)
1189
+ return void 0;
1190
+ const parsed = /^\d+$/.test(value) ? Number(value) : Date.parse(value);
1191
+ return Number.isNaN(parsed) ? null : parsed;
1192
+ };
1193
+ var outboxAdminRoutes = (runtime, pick, filter) => ({
1194
+ "GET /outbox": ({ url, namespace }) => {
1195
+ const since = parseSince(url.searchParams.get("since"));
1196
+ if (since === null) {
1197
+ return json2(400, {
1198
+ error: { type: "mockingbird_admin", message: "since: expected epoch ms or ISO-8601" }
1199
+ });
1200
+ }
1201
+ const limit = url.searchParams.get("limit");
1202
+ const where = filter?.(url.searchParams);
1203
+ const to = url.searchParams.get("to");
1204
+ return json2(200, {
1205
+ messages: pick(runtime.instance(namespace)).list({
1206
+ ...to !== null ? { to } : {},
1207
+ ...since !== void 0 ? { since } : {},
1208
+ ...where ? { where } : {},
1209
+ ...limit !== null && /^\d+$/.test(limit) ? { limit: Number(limit) } : {}
1210
+ })
1211
+ });
1212
+ },
1213
+ "GET /outbox/:id": ({ params, namespace }) => {
1214
+ const item = pick(runtime.instance(namespace)).get(params.id);
1215
+ return item ? json2(200, item) : json2(404, { error: { type: "mockingbird_admin", message: `no message ${params.id}` } });
1216
+ }
1217
+ });
1218
+
1152
1219
  // ../../core/dist/timeline.js
1153
1220
  var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
1154
1221
  var Timeline = class {
@@ -1407,6 +1474,90 @@ var operationMetadata = (operation) => {
1407
1474
 
1408
1475
  // ../core/dist/service.js
1409
1476
  import { Hono } from "hono";
1477
+
1478
+ // ../core/dist/validation.js
1479
+ var requestBodyOf = (context) => {
1480
+ const requestBody = context.operation.operation.requestBody;
1481
+ return requestBody ? deref(context.document, requestBody) : void 0;
1482
+ };
1483
+ var mediaTypeMatches = (accepted, actual) => {
1484
+ const pattern = mediaTypeOf(accepted) ?? accepted.toLowerCase();
1485
+ if (pattern === actual || pattern === "*/*")
1486
+ return true;
1487
+ const [type, subtype] = pattern.split("/");
1488
+ const [actualType, actualSubtype] = actual.split("/");
1489
+ if (type !== actualType && type !== "*")
1490
+ return false;
1491
+ if (subtype === void 0 || subtype === "*")
1492
+ return true;
1493
+ if (subtype.startsWith("*+"))
1494
+ return actualSubtype?.endsWith(subtype.slice(1)) === true;
1495
+ return false;
1496
+ };
1497
+ var acceptedKeyFor = (content, mediaType) => {
1498
+ if (!content || mediaType === void 0)
1499
+ return void 0;
1500
+ const keys = Object.keys(content);
1501
+ return keys.find((key) => mediaTypeOf(key) === mediaType) ?? keys.find((key) => mediaTypeMatches(key, mediaType));
1502
+ };
1503
+ var unsupportedMediaType = (context, options = {}) => {
1504
+ if (context.body.kind === "empty" && options.includeEmpty !== true)
1505
+ return void 0;
1506
+ const content = requestBodyOf(context)?.content;
1507
+ if (!content || Object.keys(content).length === 0)
1508
+ return void 0;
1509
+ const mediaType = mediaTypeOf(context.request.headers.get("content-type"));
1510
+ if (acceptedKeyFor(content, mediaType) !== void 0)
1511
+ return void 0;
1512
+ return { mediaType: mediaType ?? null, accepted: Object.keys(content) };
1513
+ };
1514
+ var recorded = /* @__PURE__ */ new WeakMap();
1515
+ var recordedIssues = (request) => recorded.get(request);
1516
+ var bodyIssues = (context, contentType = "application/json") => {
1517
+ const issues = findBodyIssues(context, contentType);
1518
+ if (issues.length > 0)
1519
+ recorded.set(context.request, issues);
1520
+ return issues;
1521
+ };
1522
+ var findBodyIssues = (context, contentType) => {
1523
+ const resolved = requestBodyOf(context);
1524
+ if (!resolved)
1525
+ return [];
1526
+ const unsupported = unsupportedMediaType(context);
1527
+ if (unsupported) {
1528
+ return [
1529
+ {
1530
+ path: "",
1531
+ kind: "media_type",
1532
+ message: unsupported.mediaType === null ? "request body has no content-type" : `request body media type ${unsupported.mediaType} is not supported`
1533
+ }
1534
+ ];
1535
+ }
1536
+ const key = acceptedKeyFor(resolved.content, mediaTypeOf(context.request.headers.get("content-type"))) ?? contentType;
1537
+ const schema = resolved.content?.[key]?.schema;
1538
+ if (!schema)
1539
+ return [];
1540
+ if (context.body.kind === "invalid") {
1541
+ return [
1542
+ {
1543
+ path: "",
1544
+ kind: "syntax",
1545
+ message: `request body is not valid ${context.body.mediaType}`
1546
+ }
1547
+ ];
1548
+ }
1549
+ const value = context.body.kind === "json" || context.body.kind === "form" || context.body.kind === "text" ? context.body.value : void 0;
1550
+ if (value === void 0) {
1551
+ return resolved.required && context.body.kind === "empty" ? [{ path: "", kind: "required", message: "request body is required" }] : [];
1552
+ }
1553
+ return validateValue(context.document, resolveSchema(context.document, schema), value).map((issue) => ({ path: issue.path.join("."), kind: "schema", message: issue.message }));
1554
+ };
1555
+
1556
+ // ../core/dist/service.js
1557
+ var withIssues = (request, response) => {
1558
+ const issues = response.status >= 400 ? recordedIssues(request) : void 0;
1559
+ return issues && issues.length > 0 ? annotateResponse(response, { issues }) : response;
1560
+ };
1410
1561
  var defineOperations = (handlers) => handlers;
1411
1562
  var OperationRegistryError = class extends Error {
1412
1563
  problems;
@@ -1492,9 +1643,7 @@ var createService = (options) => {
1492
1643
  now
1493
1644
  };
1494
1645
  const short = await options.before?.(context);
1495
- if (short)
1496
- return short;
1497
- return handler(context);
1646
+ return withIssues(request, short ?? await handler(context));
1498
1647
  };
1499
1648
  app.on(operation.method.toUpperCase(), honoPath(operation.path), route);
1500
1649
  }
@@ -1531,7 +1680,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
1531
1680
  };
1532
1681
 
1533
1682
  // ../core/dist/version.js
1534
- var PACKAGE_VERSION = true ? "1.0.0" : UNRELEASED_VERSION;
1683
+ var PACKAGE_VERSION = true ? "1.1.1" : UNRELEASED_VERSION;
1535
1684
 
1536
1685
  // ../core/dist/signing.js
1537
1686
  var encoder = new TextEncoder();
@@ -1854,8 +2003,8 @@ var createWebhookHub = (options) => {
1854
2003
  };
1855
2004
  return hub;
1856
2005
  };
1857
- var json2 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
1858
- var adminError2 = (status, message) => json2(status, { error: { type: "mockingbird_admin", message } });
2006
+ var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2007
+ var adminError2 = (status, message) => json3(status, { error: { type: "mockingbird_admin", message } });
1859
2008
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1860
2009
  var parseEndpoint = (value) => {
1861
2010
  if (!isRecord3(value) || typeof value.url !== "string")
@@ -1886,7 +2035,7 @@ var parseEndpoint = (value) => {
1886
2035
  return endpoint;
1887
2036
  };
1888
2037
  var webhookAdminRoutes = (hub) => ({
1889
- "GET /webhooks": ({ url, namespace }) => json2(200, {
2038
+ "GET /webhooks": ({ url, namespace }) => json3(200, {
1890
2039
  deliveries: hub.deliveries(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((d) => {
1891
2040
  const type = url.searchParams.get("type");
1892
2041
  return type === null || d.type === type;
@@ -1894,17 +2043,17 @@ var webhookAdminRoutes = (hub) => ({
1894
2043
  }),
1895
2044
  "GET /webhooks/events": ({ url, namespace }) => {
1896
2045
  const type = url.searchParams.get("type");
1897
- return json2(200, {
2046
+ return json3(200, {
1898
2047
  events: hub.messages(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((m) => type === null || m.type === type).map((m) => ({ ...m, payload: parsePayload(m) }))
1899
2048
  });
1900
2049
  },
1901
2050
  "POST /webhooks/:id/replay": async ({ params }) => {
1902
2051
  const replayed = await hub.replay(params.id);
1903
- return replayed ? json2(200, replayed) : adminError2(404, `no delivery ${params.id}`);
2052
+ return replayed ? json3(200, replayed) : adminError2(404, `no delivery ${params.id}`);
1904
2053
  },
1905
2054
  "POST /webhooks/flush": async () => {
1906
2055
  await hub.flush();
1907
- return json2(200, { status: "ok" });
2056
+ return json3(200, { status: "ok" });
1908
2057
  },
1909
2058
  "POST /webhooks/faults": ({ body, namespace }) => {
1910
2059
  if (!isRecord3(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
@@ -1914,9 +2063,9 @@ var webhookAdminRoutes = (hub) => ({
1914
2063
  if (typeof body.count === "number")
1915
2064
  fault.count = body.count;
1916
2065
  hub.fault(namespace, fault);
1917
- return json2(201, { namespace, ...fault });
2066
+ return json3(201, { namespace, ...fault });
1918
2067
  },
1919
- "GET /webhook-endpoints": ({ namespace }) => json2(200, {
2068
+ "GET /webhook-endpoints": ({ namespace }) => json3(200, {
1920
2069
  endpoints: hub.endpoints(namespace).map(({ secret, ...rest }) => ({
1921
2070
  ...rest,
1922
2071
  secret: secret ? "(set)" : null
@@ -1934,11 +2083,11 @@ var webhookAdminRoutes = (hub) => ({
1934
2083
  parsed.push(endpoint);
1935
2084
  }
1936
2085
  const set = hub.setEndpoints(namespace, parsed);
1937
- return json2(200, { endpoints: set.map((e) => ({ ...e, secret: e.secret ? "(set)" : null })) });
2086
+ return json3(200, { endpoints: set.map((e) => ({ ...e, secret: e.secret ? "(set)" : null })) });
1938
2087
  },
1939
2088
  "DELETE /webhook-endpoints": ({ namespace }) => {
1940
2089
  hub.setEndpoints(namespace, []);
1941
- return json2(200, { status: "ok" });
2090
+ return json3(200, { status: "ok" });
1942
2091
  }
1943
2092
  });
1944
2093
  var parsePayload = (message) => {
@@ -2291,8 +2440,15 @@ var createRuntime = (options) => {
2291
2440
  const started = monotonicNow();
2292
2441
  const url = new URL(request.url);
2293
2442
  const operationId = operationIdFor(request, url.pathname);
2443
+ const contentLength = request.headers.get("content-length");
2444
+ const received = {
2445
+ contentType: request.headers.get("content-type"),
2446
+ bodyBytes: contentLength !== null && /^\d+$/.test(contentLength) ? Number(contentLength) : null,
2447
+ transferEncoding: request.headers.get("transfer-encoding")
2448
+ };
2294
2449
  const log = (status, faultId, response2) => {
2295
2450
  const noted = response2 ? responseNotes(response2) : void 0;
2451
+ const rejected = status >= 400 && faultId === void 0;
2296
2452
  const entry = {
2297
2453
  service: options.name,
2298
2454
  namespace,
@@ -2304,7 +2460,9 @@ var createRuntime = (options) => {
2304
2460
  unmatched: options.document !== void 0 && operationId === void 0,
2305
2461
  ...faultId !== void 0 ? { faultId } : {},
2306
2462
  ...noted?.ids && Object.keys(noted.ids).length > 0 ? { ids: noted.ids } : {},
2307
- ...noted?.adopted ? { adopted: true } : {}
2463
+ ...noted?.adopted ? { adopted: true } : {},
2464
+ ...rejected ? { request: { ...received } } : {},
2465
+ ...rejected && noted?.issues && noted.issues.length > 0 ? { issues: noted.issues.map((issue) => ({ ...issue })) } : {}
2308
2466
  };
2309
2467
  metrics.record(entry);
2310
2468
  journal.record({ ...entry, at: new Date(clock.now()).toISOString() });
@@ -2511,25 +2669,6 @@ var presetRoutes = (presets, runtime) => ({
2511
2669
  }
2512
2670
  });
2513
2671
 
2514
- // ../core/dist/validation.js
2515
- var bodyIssues = (context, contentType = "application/json") => {
2516
- const requestBody = context.operation.operation.requestBody;
2517
- if (!requestBody)
2518
- return [];
2519
- const resolved = deref(context.document, requestBody);
2520
- const schema = resolved.content?.[contentType]?.schema;
2521
- if (!schema)
2522
- return [];
2523
- const value = context.body.kind === "json" || context.body.kind === "form" ? context.body.value : void 0;
2524
- if (context.body.kind === "invalid") {
2525
- return [{ path: "", message: `request body is not valid ${context.body.mediaType}` }];
2526
- }
2527
- if (value === void 0) {
2528
- return resolved.required ? [{ path: "", message: "request body is required" }] : [];
2529
- }
2530
- return validateValue(context.document, resolveSchema(context.document, schema), value).map((issue) => ({ path: issue.path.join("."), message: issue.message }));
2531
- };
2532
-
2533
2672
  // src/generated/openapi.ts
2534
2673
  var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Intercom REST API 2.11 (Mockingbird subset)","description":"Stateful mock subset of the Intercom REST API (version 2.11): contacts (search, create,\\nupdate, get), conversations (create, update, reply as user or admin with attachments,\\nclose/open/snooze/assign, get, search) and the admin identity endpoints. Covers the backend's\\nmember messaging adapter and the Intercom sync adapter.\\n","version":"2.11","x-mockingbird-upstream":{"note":"Trimmed from Intercom's published 2.11 API reference to the operations and fields our consumers use (intercom-messaging.adapter.ts, intercom-api.adapter.ts) and the webhook receivers (messaging.controller.ts, the EMR IntercomWebhookService)."}},"servers":[{"url":"https://api.intercom.io"}],"security":[{"bearerAuth":[]}],"paths":{"/contacts/search":{"post":{"operationId":"SearchContacts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ContactQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"responses":{"200":{"description":"Matching contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/contacts":{"post":{"operationId":"CreateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"200":{"description":"The created contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/contacts/{contact_id}":{"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}],"get":{"operationId":"GetContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdate"}}}},"responses":{"200":{"description":"The updated contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations":{"post":{"operationId":"CreateConversation","description":"A contact-initiated conversation. An optional \`Idempotency-Key\` header replays.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,40}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","body"],"properties":{"from":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string","enum":["user","lead","contact"]},"id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}},"body":{"type":"string","minLength":1,"maxLength":400},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000}}}}}},"responses":{"200":{"description":"The first message of the new conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations/search":{"post":{"operationId":"SearchConversations","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ConversationQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"},"sort_field":{"type":"string","enum":["updated_at","created_at","id","waiting_since"]},"sort_order":{"type":"string","enum":["ascending","descending"]}}}}}},"responses":{"200":{"description":"A page of matching conversations (without conversation parts)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/conversations/{conversation_id}":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"get":{"operationId":"GetConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"responses":{"200":{"description":"The conversation with its parts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean"},"title":{"type":"string","maxLength":120},"custom_attributes":{"type":"object","maxProperties":4,"additionalProperties":{"type":["string","number","boolean","null"]}}}}}}},"responses":{"200":{"description":"The updated conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/reply":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ReplyConversation","description":"Reply as the contact (\`type: user\` + \`intercom_user_id\`) or as an admin (\`type: admin\` + \`admin_id\`), as JSON (attachments as base64 \`attachment_files\`) or multipart (\`attachment_files[]\` file parts). Admin comments fire \`conversation.admin.replied\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyBody"}},"multipart/form-data":{"schema":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string"},"type":{"type":"string"},"intercom_user_id":{"type":"string"},"admin_id":{"type":"string"},"body":{"type":"string"},"attachment_files[]":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/parts":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ManageConversation","description":"Close, open, snooze or assign; close and open fire \`conversation.admin.closed\` / \`opened\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message_type","type","admin_id"],"properties":{"message_type":{"type":"string","enum":["close","open","snoozed","assignment"]},"type":{"type":"string","enum":["admin"]},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"body":{"type":"string","maxLength":400},"snoozed_until":{"type":"integer","minimum":1600000000,"maximum":1900000000},"assignee_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admins":{"get":{"operationId":"ListAdmins","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The workspace's admins","content":{"application/json":{"schema":{"type":"object","required":["type","admins"],"properties":{"type":{"type":"string","enum":["admin.list"]},"admins":{"type":"array","items":{"$ref":"#/components/schemas/Admin"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me":{"get":{"operationId":"GetMe","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The admin that owns the access token","content":{"application/json":{"schema":{"type":"object","required":["type","id","email","app"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"email_verified":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"avatar":{"type":"object"},"app":{"type":"object","required":["type","id_code"],"properties":{"type":{"type":"string"},"id_code":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"integer"},"secure":{"type":"boolean"},"identity_verification":{"type":"boolean"},"timezone":{"type":"string"},"region":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"ConversationId":{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"conversation","missing":"999999999999999"}}},"DisplayAs":{"name":"display_as","in":"query","schema":{"type":"string","enum":["plaintext"]}}},"responses":{"BadRequest":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Unauthorized":{"description":"Missing or invalid access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"NotFound":{"description":"Unknown resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Conflict":{"description":"A contact with that external_id or email already exists (or an idempotency conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}}},"schemas":{"ErrorList":{"type":"object","required":["type","errors"],"properties":{"type":{"type":"string","enum":["error.list"]},"request_id":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"errors":{"type":"array","minItems":1,"items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"Pagination":{"type":"object","properties":{"per_page":{"type":"integer","minimum":1,"maximum":150},"starting_after":{"type":"string","maxLength":80}}},"ContactFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["external_id","email","id","name","role","phone"]},"operator":{"type":"string","enum":["=","!=","~","!~","^","$","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ContactQuery":{"oneOf":[{"$ref":"#/components/schemas/ContactFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ContactFilter"}}}}]},"ConversationFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["id","contact_ids","admin_assignee_id","team_assignee_id","state","open","read","created_at","updated_at","source.author.email","source.delivered_as"]},"operator":{"type":"string","enum":["=","!=","<",">","~","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"integer"},{"type":"boolean"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ConversationQuery":{"oneOf":[{"$ref":"#/components/schemas/ConversationFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ConversationFilter"}}}}]},"CustomAttributes":{"type":"object","maxProperties":30,"additionalProperties":{"type":["string","number","boolean","null"]}},"ContactCreate":{"type":"object","properties":{"role":{"type":"string","enum":["user","lead"]},"external_id":{"description":"Any string. The first branch steers the walk generator toward a few values, so random walks reach the duplicate (409) path.","anyOf":[{"type":"string","pattern":"^[1-4]$"},{"type":"string","minLength":1,"maxLength":64}]},"email":{"type":"string","format":"email","maxLength":120},"name":{"type":["string","null"],"maxLength":80},"phone":{"type":["string","null"],"maxLength":40},"signed_up_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"last_seen_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"custom_attributes":{"$ref":"#/components/schemas/CustomAttributes"}}},"ContactUpdate":{"allOf":[{"$ref":"#/components/schemas/ContactCreate"}]},"ReplyBody":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string","enum":["comment","note","quick_reply"]},"type":{"type":"string","enum":["user","admin"]},"body":{"type":"string","maxLength":400},"intercom_user_id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}},"user_id":{"type":"string","maxLength":64},"email":{"type":"string","maxLength":120},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000},"attachment_urls":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":200}},"attachment_files":{"type":"array","maxItems":3,"items":{"type":"object","required":["content_type","data","name"],"properties":{"content_type":{"type":"string","maxLength":80},"data":{"type":"string","maxLength":400},"name":{"type":"string","minLength":1,"maxLength":80}}}}}},"Contact":{"type":"object","required":["type","id","role","created_at","updated_at","custom_attributes"],"properties":{"type":{"type":"string","enum":["contact"]},"id":{"type":"string","x-mockingbird-resource":{"type":"contact","identity":true}},"workspace_id":{"type":"string"},"external_id":{"type":["string","null"]},"role":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"name":{"type":["string","null"]},"avatar":{"type":["string","null"]},"owner_id":{"type":["integer","null"]},"has_hard_bounced":{"type":"boolean"},"marked_email_as_spam":{"type":"boolean"},"unsubscribed_from_emails":{"type":"boolean"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"signed_up_at":{"type":["integer","null"]},"last_seen_at":{"type":["integer","null"]},"custom_attributes":{"type":"object"},"tags":{"type":"object"},"notes":{"type":"object"},"companies":{"type":"object"},"location":{"type":"object"},"social_profiles":{"type":"object"}}},"Pages":{"type":"object","required":["type","page","per_page","total_pages"],"properties":{"type":{"type":"string","enum":["pages"]},"page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"},"next":{"type":"object","required":["page","starting_after"],"properties":{"page":{"type":"integer"},"starting_after":{"type":"string"}}}}},"ContactList":{"type":"object","required":["type","data","total_count","pages"],"properties":{"type":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total_count":{"type":"integer"},"pages":{"$ref":"#/components/schemas/Pages"}}},"Author":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"name":{"type":["string","null"]},"email":{"type":["string","null"]}}},"Attachment":{"type":"object","required":["type","name","url","content_type"],"properties":{"type":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"content_type":{"type":"string"},"filesize":{"type":"integer"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]}}},"ConversationPart":{"type":"object","required":["type","id","part_type","created_at","author"],"properties":{"type":{"type":"string","enum":["conversation_part"]},"id":{"type":"string"},"part_type":{"type":"string"},"body":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"notified_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"assigned_to":{"type":["object","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"external_id":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"Conversation":{"type":"object","required":["type","id","created_at","updated_at","open","state","read","source","contacts"],"properties":{"type":{"type":"string","enum":["conversation"]},"id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}},"title":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"waiting_since":{"type":["integer","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"snoozed_until":{"type":["integer","null"]},"open":{"type":"boolean"},"state":{"type":"string","enum":["open","closed","snoozed"]},"read":{"type":"boolean"},"priority":{"type":"string"},"admin_assignee_id":{"type":["integer","null"]},"team_assignee_id":{"type":["string","null"]},"tags":{"type":"object"},"conversation_rating":{"type":["object","null"]},"source":{"type":"object","required":["type","id","delivered_as","body","author"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"delivered_as":{"type":"string"},"subject":{"type":"string"},"body":{"type":["string","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"url":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"contacts":{"type":"object","required":["type","contacts"],"properties":{"type":{"type":"string"},"contacts":{"type":"array","items":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"external_id":{"type":["string","null"]}}}}}},"teammates":{"type":"object"},"custom_attributes":{"type":"object"},"first_contact_reply":{"type":["object","null"]},"sla_applied":{"type":["object","null"]},"statistics":{"type":["object","null"]},"ai_agent_participated":{"type":"boolean"},"conversation_parts":{"type":"object","required":["type","conversation_parts","total_count"],"properties":{"type":{"type":"string"},"conversation_parts":{"type":"array","items":{"$ref":"#/components/schemas/ConversationPart"}},"total_count":{"type":"integer"}}}}},"ConversationList":{"type":"object","required":["type","pages","total_count","conversations"],"properties":{"type":{"type":"string","enum":["conversation.list"]},"pages":{"$ref":"#/components/schemas/Pages"},"total_count":{"type":"integer"},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}}},"Message":{"type":"object","required":["type","id","created_at","body","message_type","conversation_id"],"properties":{"type":{"type":"string","enum":["user_message"]},"id":{"type":"string"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"body":{"type":"string"},"message_type":{"type":"string","enum":["inapp"]},"conversation_id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}}}},"Admin":{"type":"object","required":["type","id","name","email"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string","x-mockingbird-resource":{"type":"admin","identity":true}},"name":{"type":"string"},"email":{"type":"string"},"job_title":{"type":["string","null"]},"away_mode_enabled":{"type":"boolean"},"away_mode_reassign":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"team_ids":{"type":"array","items":{"type":"integer"}}}}}}}`);
2535
2674
  var operationIds = ["SearchContacts", "CreateContact", "GetContact", "UpdateContact", "CreateConversation", "SearchConversations", "GetConversation", "UpdateConversation", "ReplyConversation", "ManageConversation", "ListAdmins", "GetMe"];
@@ -2683,6 +2822,7 @@ var IntercomState = class {
2683
2822
  this.admins = new Collection(sqlite, namespace, "admins");
2684
2823
  this.settings = new Collection(sqlite, namespace, "settings");
2685
2824
  this.counters = new Collection(sqlite, namespace, "counters");
2825
+ this.outbox = new OutboxStore(sqlite, namespace, "outbox");
2686
2826
  this.workspaceId = "mockapp";
2687
2827
  this.ensureSeeded();
2688
2828
  }
@@ -2693,6 +2833,7 @@ var IntercomState = class {
2693
2833
  admins;
2694
2834
  settings;
2695
2835
  counters;
2836
+ outbox;
2696
2837
  workspaceId;
2697
2838
  ensureSeeded() {
2698
2839
  if (this.admins.count() === 0) {
@@ -2810,8 +2951,8 @@ var INTERCOM_PRESETS = {
2810
2951
  webhook: { mode: "drop" }
2811
2952
  }
2812
2953
  };
2813
- var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2814
- var adminError3 = (status, message) => json3(status, { error: { type: "mockingbird_admin", message } });
2954
+ var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2955
+ var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
2815
2956
  var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2816
2957
  var guard = (run) => {
2817
2958
  try {
@@ -2826,8 +2967,9 @@ var adminRoutes = (runtime) => {
2826
2967
  const conversation = (namespace, id) => api(namespace).state.conversations.get(id);
2827
2968
  const defaultAdmin = (namespace) => api(namespace).state.admins.list({ order: "oldest" }).at(0)?.value.id ?? "";
2828
2969
  return {
2829
- "GET /contacts": ({ namespace }) => json3(200, { contacts: api(namespace).contacts() }),
2830
- "GET /conversations": ({ namespace }) => json3(200, { conversations: api(namespace).conversations() }),
2970
+ ...outboxAdminRoutes(runtime, (instance) => instance.state.outbox),
2971
+ "GET /contacts": ({ namespace }) => json4(200, { contacts: api(namespace).contacts() }),
2972
+ "GET /conversations": ({ namespace }) => json4(200, { conversations: api(namespace).conversations() }),
2831
2973
  "POST /conversations": ({ body, namespace }) => guard(() => {
2832
2974
  if (!isRecord5(body) || typeof body.body !== "string") {
2833
2975
  return adminError3(400, 'expected {"contactId" | "externalId", "adminId"?, "body"}');
@@ -2840,7 +2982,7 @@ var adminRoutes = (runtime) => {
2840
2982
  adminId: typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace),
2841
2983
  body: body.body
2842
2984
  });
2843
- return json3(201, created);
2985
+ return json4(201, created);
2844
2986
  }),
2845
2987
  "POST /conversations/:id/admin-reply": ({ params, body, namespace }) => guard(() => {
2846
2988
  const found = conversation(namespace, params.id);
@@ -2848,32 +2990,33 @@ var adminRoutes = (runtime) => {
2848
2990
  if (!isRecord5(body) || typeof body.body !== "string") {
2849
2991
  return adminError3(
2850
2992
  400,
2851
- 'expected {"adminId"?, "body", "messageType"?: "comment" | "note"}'
2993
+ 'expected {"adminId" | "admin_id"?, "body", "messageType"?: "comment" | "note"}'
2852
2994
  );
2853
2995
  }
2854
2996
  const instance = api(namespace);
2997
+ const requested = body.adminId ?? body.admin_id;
2855
2998
  const author = instance.state.admins.get(
2856
- typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace)
2999
+ typeof requested === "string" ? requested : defaultAdmin(namespace)
2857
3000
  );
2858
- if (!author) return adminError3(404, `no admin ${String(body.adminId)}`);
3001
+ if (!author) return adminError3(404, `no admin ${String(requested)}`);
2859
3002
  const next = instance.appendPart(found, {
2860
3003
  partType: body.messageType === "note" ? "note" : "comment",
2861
3004
  author: { type: "admin", id: author.id, name: author.name, email: author.email },
2862
3005
  body: toHtml(body.body)
2863
3006
  });
2864
- return json3(200, next);
3007
+ return json4(200, next);
2865
3008
  }),
2866
3009
  "POST /conversations/:id/close": ({ params, body, namespace }) => guard(() => {
2867
3010
  const found = conversation(namespace, params.id);
2868
3011
  if (!found) return adminError3(404, `no conversation ${params.id}`);
2869
3012
  const adminId = isRecord5(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
2870
- return json3(200, api(namespace).manage(found, { action: "close", adminId }));
3013
+ return json4(200, api(namespace).manage(found, { action: "close", adminId }));
2871
3014
  }),
2872
3015
  "POST /conversations/:id/open": ({ params, body, namespace }) => guard(() => {
2873
3016
  const found = conversation(namespace, params.id);
2874
3017
  if (!found) return adminError3(404, `no conversation ${params.id}`);
2875
3018
  const adminId = isRecord5(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
2876
- return json3(200, api(namespace).manage(found, { action: "open", adminId }));
3019
+ return json4(200, api(namespace).manage(found, { action: "open", adminId }));
2877
3020
  }),
2878
3021
  "PUT /admins": ({ body, namespace }) => {
2879
3022
  const list = Array.isArray(body) ? body : isRecord5(body) ? body.admins : void 0;
@@ -2895,9 +3038,9 @@ var adminRoutes = (runtime) => {
2895
3038
  team_ids: []
2896
3039
  });
2897
3040
  }
2898
- return json3(200, { admins: state.admins.list({ order: "oldest" }).map((row) => row.value) });
3041
+ return json4(200, { admins: state.admins.list({ order: "oldest" }).map((row) => row.value) });
2899
3042
  },
2900
- "GET /settings": ({ namespace }) => json3(200, api(namespace).state.current()),
3043
+ "GET /settings": ({ namespace }) => json4(200, api(namespace).state.current()),
2901
3044
  "PUT /settings": ({ body, namespace }) => {
2902
3045
  if (!isRecord5(body)) return adminError3(400, "expected a JSON object");
2903
3046
  const patch = {};
@@ -2911,7 +3054,7 @@ var adminRoutes = (runtime) => {
2911
3054
  }
2912
3055
  patch.customAttributes = body.customAttributes === null ? null : body.customAttributes.map(String);
2913
3056
  }
2914
- return json3(200, api(namespace).state.update(patch));
3057
+ return json4(200, api(namespace).state.update(patch));
2915
3058
  }
2916
3059
  };
2917
3060
  };
@@ -2924,14 +3067,15 @@ var createRuntime2 = (options = {}) => {
2924
3067
  ...hooks?.retryDelaysMs ? { retryDelaysMs: hooks.retryDelaysMs } : {},
2925
3068
  ...hooks?.fetch ? { fetch: hooks.fetch } : {},
2926
3069
  ...options.wallClock ? { now: options.wallClock } : {},
2927
- endpoints: (hooks?.urls ?? []).map(
2928
- (url, index) => ({
3070
+ endpoints: (hooks?.urls ?? []).map((entry, index) => {
3071
+ const target = typeof entry === "string" ? { url: entry } : entry;
3072
+ return {
2929
3073
  id: `we_intercom_${index}`,
2930
- url,
3074
+ url: target.url,
2931
3075
  ...hooks?.secret ? { secret: hooks.secret } : {},
2932
- events: [...hooks?.events ?? DEFAULT_TOPICS]
2933
- })
2934
- )
3076
+ events: [...target.events ?? hooks?.events ?? DEFAULT_TOPICS]
3077
+ };
3078
+ })
2935
3079
  });
2936
3080
  const runtime = createRuntime({
2937
3081
  name: INTERCOM_NAMESPACE,
@@ -2972,7 +3116,9 @@ var INTERCOM_TOPICS = [
2972
3116
  "conversation.admin.opened",
2973
3117
  "conversation.admin.snoozed",
2974
3118
  "conversation.admin.assigned",
2975
- "conversation.admin.single.created"
3119
+ "conversation.admin.single.created",
3120
+ "conversation.user.created",
3121
+ "conversation.user.replied"
2976
3122
  ];
2977
3123
  var CONTACT_FIELDS = [
2978
3124
  "id",
@@ -3407,6 +3553,17 @@ var IntercomAPI = class {
3407
3553
  parts: []
3408
3554
  };
3409
3555
  this.state.conversations.insert(conversation.id, conversation);
3556
+ const text2 = String(body.body);
3557
+ this.recordSent({
3558
+ operation: "CreateConversation",
3559
+ conversation,
3560
+ partId: null,
3561
+ partType: "source",
3562
+ author: conversation.source.author,
3563
+ bodyLength: text2.length,
3564
+ attachmentCount: 0
3565
+ });
3566
+ this.notify("conversation.user.created", conversation, []);
3410
3567
  return annotateResponse(
3411
3568
  jsonRes(200, {
3412
3569
  type: "user_message",
@@ -3577,6 +3734,15 @@ var IntercomAPI = class {
3577
3734
  body: body === void 0 ? null : toHtml(body),
3578
3735
  attachments: stored
3579
3736
  });
3737
+ this.recordSent({
3738
+ operation: "ReplyConversation",
3739
+ conversation: next,
3740
+ partId,
3741
+ partType: messageType,
3742
+ author,
3743
+ bodyLength: body?.length ?? 0,
3744
+ attachmentCount: stored.length
3745
+ });
3580
3746
  return annotateResponse(jsonRes(200, this.conversationBody(next, { parts: next.parts })), {
3581
3747
  ids: { conversationId: next.id, partId }
3582
3748
  });
@@ -3611,8 +3777,29 @@ var IntercomAPI = class {
3611
3777
  };
3612
3778
  this.state.conversations.update(conversation.id, next);
3613
3779
  if (byAdmin && visible) this.notify("conversation.admin.replied", next, [part]);
3780
+ if (input.author.type === "user") this.notify("conversation.user.replied", next, [part]);
3614
3781
  return next;
3615
3782
  }
3783
+ /** Record what the API client sent, for `GET /__admin/outbox`: metadata only, never the body. */
3784
+ recordSent(input) {
3785
+ const at = new Date(this.now()).toISOString();
3786
+ this.state.outbox.record({
3787
+ id: `sent_${this.state.next("sent")}`,
3788
+ to: [input.conversation.id, input.conversation.contactId],
3789
+ createdAt: at,
3790
+ at,
3791
+ operation: input.operation,
3792
+ conversationId: input.conversation.id,
3793
+ contactId: input.conversation.contactId,
3794
+ partId: input.partId,
3795
+ partType: input.partType,
3796
+ authorType: input.author.type === "admin" ? "admin" : "user",
3797
+ authorId: input.author.id,
3798
+ hasBody: input.bodyLength > 0,
3799
+ bodyLength: input.bodyLength,
3800
+ attachmentCount: input.attachmentCount
3801
+ });
3802
+ }
3616
3803
  manageConversation(context) {
3617
3804
  return this.withConversation(context, (conversation) => {
3618
3805
  const body = this.json(context);
@@ -3832,4 +4019,4 @@ export {
3832
4019
  IntercomError,
3833
4020
  IntercomAPI
3834
4021
  };
3835
- //# sourceMappingURL=chunk-OSBG2XJ7.js.map
4022
+ //# sourceMappingURL=chunk-G2BMC225.js.map