@hraness/message-like-me 0.8.3 → 0.8.5

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.
@@ -8,10 +8,10 @@ import {
8
8
  buildEnsoulMessagesSourcePacketV1,
9
9
  ensoulSubjectMessages,
10
10
  ensoulSubjectReactions
11
- } from "./cli-x1qncxm7.js";
12
- import"./cli-qqafdvz9.js";
13
- import"./cli-kw20gkk3.js";
14
- import"./cli-ththzwja.js";
11
+ } from "./index-7dfvy41v.js";
12
+ import"./index-qqafdvz9.js";
13
+ import"./index-kw20gkk3.js";
14
+ import"./index-y1aec88m.js";
15
15
  export {
16
16
  ensoulSubjectReactions,
17
17
  ensoulSubjectMessages,
@@ -1,17 +1,14 @@
1
1
  // @bun
2
2
  import {
3
3
  MESSAGE_BUNDLE_V1_SCHEMA_IDENTITY
4
- } from "./cli-qqafdvz9.js";
4
+ } from "./index-qqafdvz9.js";
5
5
  import {
6
6
  MESSAGE_BUNDLE_V2_SCHEMA_IDENTITY
7
- } from "./cli-kw20gkk3.js";
7
+ } from "./index-kw20gkk3.js";
8
8
  import {
9
9
  canonicalJson,
10
10
  sha256
11
- } from "./cli-ththzwja.js";
12
-
13
- // src/metrics.ts
14
- import { createHash } from "crypto";
11
+ } from "./index-y1aec88m.js";
15
12
 
16
13
  // src/types.ts
17
14
  var CORPUS_SCHEMA_VERSION = 2;
@@ -39,26 +36,12 @@ var MAX_STUDY_MESSAGES_PER_DIRECTION = 64;
39
36
  var DEFAULT_MAX_STUDY_PACKET_BODY_BYTES = 256 * 1024;
40
37
  var MAX_STUDY_PACKET_BODY_BYTES = 1024 * 1024;
41
38
  var MAX_GAP_SECONDS = 30 * 24 * 60 * 60;
42
- function digest(namespace, parts) {
43
- const hash = createHash("sha256");
44
- hash.update(`message-like-me\x00${namespace}\x00`, "utf8");
45
- for (const part of parts)
46
- hash.update(`${part.length}:`, "utf8").update(part, "utf8");
47
- return hash.digest("hex");
48
- }
49
39
  function round(value, places = 6) {
50
40
  if (!Number.isFinite(value))
51
41
  return 0;
52
42
  const scale = 10 ** places;
53
43
  return Math.round((value + Number.EPSILON) * scale) / scale;
54
44
  }
55
- function boundedGap(value, fallback, label) {
56
- const result = value ?? fallback;
57
- if (!Number.isSafeInteger(result) || result < 1 || result > MAX_GAP_SECONDS) {
58
- throw new Error(`${label} must be an integer from 1 through ${MAX_GAP_SECONDS}`);
59
- }
60
- return result;
61
- }
62
45
  function boundedStudyInteger(value, fallback, maximum, label) {
63
46
  const result = value ?? fallback;
64
47
  if (!Number.isSafeInteger(result) || result < 1 || result > maximum) {
@@ -103,349 +86,9 @@ function orderedMessages(messages) {
103
86
  rows.sort((left, right) => left.milliseconds - right.milliseconds || left.message.sourceRowId - right.message.sourceRowId || left.message.id.localeCompare(right.message.id, "en-US"));
104
87
  return Object.freeze(rows);
105
88
  }
106
- function timelineEligible(message) {
107
- return message.retractedAt === null && (message.kind === "text" || message.kind === "attachment" || message.kind === "reaction");
108
- }
109
- function responseEligible(message) {
110
- return message.retractedAt === null && (message.kind === "text" || message.kind === "attachment");
111
- }
112
- function secondsBetween(left, right) {
113
- return Math.max(0, (right.milliseconds - left.milliseconds) / 1000);
114
- }
115
- function sessionsFor(messages, corpusRevision, contactId, gapSeconds) {
116
- const eligible = messages.filter(({ message }) => timelineEligible(message));
117
- if (eligible.length === 0)
118
- return Object.freeze([]);
119
- const groups = [];
120
- for (const row of eligible) {
121
- const current = groups.at(-1);
122
- const prior = current?.at(-1);
123
- if (current === undefined || prior === undefined || secondsBetween(prior, row) > gapSeconds) {
124
- groups.push([row]);
125
- } else
126
- current.push(row);
127
- }
128
- return Object.freeze(groups.map((group, index) => {
129
- const first = group[0];
130
- const last = group.at(-1);
131
- const incomingCount = group.filter(({ message }) => message.direction === "incoming").length;
132
- const outgoingCount = group.length - incomingCount;
133
- return Object.freeze({
134
- id: digest("session", [corpusRevision, contactId, String(index), ...group.map(({ message }) => message.id)]),
135
- startedAt: first.message.sentAt,
136
- endedAt: last.message.sentAt,
137
- durationSeconds: round((last.milliseconds - first.milliseconds) / 1000, 3),
138
- messageCount: group.length,
139
- incomingCount,
140
- outgoingCount,
141
- startedBy: first.message.direction,
142
- endedBy: last.message.direction
143
- });
144
- }));
145
- }
146
- function blocksFor(messages, burstGapSeconds) {
147
- const eligible = messages.filter(({ message }) => responseEligible(message));
148
- const blocks = [];
149
- for (const row of eligible) {
150
- const current = blocks.at(-1);
151
- const prior = current?.messages.at(-1);
152
- if (current === undefined || prior === undefined || current.direction !== row.message.direction || secondsBetween(prior, row) > burstGapSeconds) {
153
- blocks.push({ direction: row.message.direction, messages: [row] });
154
- } else
155
- current.messages.push(row);
156
- }
157
- return Object.freeze(blocks.map((block) => Object.freeze({
158
- direction: block.direction,
159
- messages: Object.freeze(block.messages)
160
- })));
161
- }
162
- function burstsFor(messages, sessions, corpusRevision, contactId, burstGapSeconds) {
163
- const result = [];
164
- for (const session of sessions) {
165
- const started = Date.parse(session.startedAt);
166
- const ended = Date.parse(session.endedAt);
167
- const sessionRows = messages.filter((row) => row.milliseconds >= started && row.milliseconds <= ended && responseEligible(row.message));
168
- for (const block of blocksFor(sessionRows, burstGapSeconds)) {
169
- const first = block.messages[0];
170
- const last = block.messages.at(-1);
171
- const messageIds = Object.freeze(block.messages.map(({ message }) => message.id));
172
- const textBodies = bodies(block.messages);
173
- result.push(Object.freeze({
174
- metric: Object.freeze({
175
- id: digest("burst", [corpusRevision, contactId, session.id, ...messageIds]),
176
- sessionId: session.id,
177
- startedAt: first.message.sentAt,
178
- endedAt: last.message.sentAt,
179
- durationSeconds: round((last.milliseconds - first.milliseconds) / 1000, 3),
180
- direction: block.direction,
181
- messageIds,
182
- messageCount: block.messages.length,
183
- textMessageCount: textBodies.length,
184
- characters: textBodies.reduce((total, body) => total + characterCount(body), 0)
185
- }),
186
- messages: block.messages
187
- }));
188
- }
189
- }
190
- return Object.freeze(result);
191
- }
192
- function bodies(rows) {
193
- return rows.flatMap(({ message }) => message.retractedAt === null && message.kind === "text" && message.body !== null ? [message.body] : []);
194
- }
195
89
  function characterCount(value) {
196
90
  return Array.from(value).length;
197
91
  }
198
- function questionCount(value) {
199
- return value.match(/[?\uFF1F]/gu)?.length ?? 0;
200
- }
201
- function containsMultiItemBody(value) {
202
- const lines = value.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
203
- if (lines.length >= 2)
204
- return true;
205
- return /(?:^|\n)\s*(?:[-*\u2022]|[0-9]{1,2}[.)])\s+/u.test(value) || (value.match(/;/gu)?.length ?? 0) >= 2;
206
- }
207
- function responseTags(incoming, outgoing, latencySeconds, incomingQuestions, outgoingCharacters, explicitReplyCount, replyUnavailableCount) {
208
- const tags = new Set;
209
- tags.add(outgoing.length === 1 ? "single-message-response" : "multi-message-response");
210
- if (incoming.length > 1)
211
- tags.add("multi-incoming");
212
- if (incomingQuestions > 1)
213
- tags.add("multi-question");
214
- if (incoming.length > 1 || incomingQuestions > 1 || bodies(incoming).some(containsMultiItemBody))
215
- tags.add("multi-item-context");
216
- if (explicitReplyCount > 0)
217
- tags.add("explicit-reply");
218
- if (replyUnavailableCount > 0)
219
- tags.add("reply-unavailable");
220
- if (latencySeconds <= 60)
221
- tags.add("fast-response");
222
- else if (latencySeconds >= 60 * 60)
223
- tags.add("delayed-response");
224
- if (outgoingCharacters <= 40)
225
- tags.add("short-response");
226
- else if (outgoingCharacters >= 280)
227
- tags.add("long-response");
228
- if (bodies(outgoing).some((body) => body.includes(`
229
- `)))
230
- tags.add("multiline-response");
231
- return Object.freeze([...tags].sort((left, right) => left.localeCompare(right, "en-US")));
232
- }
233
- function responsesFor(bursts, corpusRevision, contactId) {
234
- const result = [];
235
- const bySession = new Map;
236
- for (const burst of bursts) {
237
- const values = bySession.get(burst.metric.sessionId) ?? [];
238
- values.push(burst);
239
- bySession.set(burst.metric.sessionId, values);
240
- }
241
- for (const sessionBursts of bySession.values()) {
242
- for (let index = 0;index + 1 < sessionBursts.length; index += 1) {
243
- const incoming = sessionBursts[index];
244
- const outgoing = sessionBursts[index + 1];
245
- if (incoming.metric.direction !== "incoming" || outgoing.metric.direction !== "outgoing")
246
- continue;
247
- const incomingBodies = bodies(incoming.messages);
248
- const outgoingBodies = bodies(outgoing.messages);
249
- const incomingCharacters = incomingBodies.reduce((total, body) => total + characterCount(body), 0);
250
- const outgoingCharacters = outgoingBodies.reduce((total, body) => total + characterCount(body), 0);
251
- const incomingQuestions = incomingBodies.reduce((total, body) => total + questionCount(body), 0);
252
- const explicitReplyCount = outgoing.messages.filter(({ message }) => message.replyState === "explicit").length;
253
- const replyEligibleCount = outgoing.messages.filter(({ message }) => message.replyState !== "unavailable").length;
254
- const replyUnavailableCount = outgoing.messages.length - replyEligibleCount;
255
- const lastIncoming = incoming.messages.at(-1);
256
- const firstOutgoing = outgoing.messages[0];
257
- const latencySeconds = round(secondsBetween(lastIncoming, firstOutgoing), 3);
258
- const incomingIds = Object.freeze(incoming.messages.map(({ message }) => message.id));
259
- const outgoingIds = Object.freeze(outgoing.messages.map(({ message }) => message.id));
260
- result.push(Object.freeze({
261
- id: digest("response", [corpusRevision, contactId, ...incomingIds, "->", ...outgoingIds]),
262
- startedAt: incoming.messages[0].message.sentAt,
263
- incomingMessageIds: incomingIds,
264
- outgoingMessageIds: outgoingIds,
265
- incomingCount: incoming.messages.length,
266
- outgoingCount: outgoing.messages.length,
267
- incomingCharacters,
268
- outgoingCharacters,
269
- incomingQuestions,
270
- latencySeconds,
271
- explicitReplyCount,
272
- replyEligibleCount,
273
- replyUnavailableCount,
274
- tags: responseTags(incoming.messages, outgoing.messages, latencySeconds, incomingQuestions, outgoingCharacters, explicitReplyCount, replyUnavailableCount)
275
- }));
276
- }
277
- }
278
- return Object.freeze(result);
279
- }
280
- function quantile(values, proportion) {
281
- if (values.length === 0)
282
- return null;
283
- const sorted = [...values].sort((left, right) => left - right);
284
- if (sorted.length === 1)
285
- return sorted[0];
286
- const position = (sorted.length - 1) * proportion;
287
- const lower = Math.floor(position);
288
- const upper = Math.ceil(position);
289
- const low = sorted[lower];
290
- const high = sorted[upper];
291
- return round(low + (high - low) * (position - lower), 6);
292
- }
293
- function numericDistribution(values) {
294
- const total = values.reduce((sum, value) => sum + value, 0);
295
- return Object.freeze({
296
- total,
297
- mean: values.length === 0 ? 0 : round(total / values.length),
298
- median: quantile(values, 0.5) ?? 0,
299
- p90: quantile(values, 0.9) ?? 0
300
- });
301
- }
302
- function wordCount(value) {
303
- return value.match(/[\p{L}\p{N}]+(?:['\u2019][\p{L}\p{N}]+)*/gu)?.length ?? 0;
304
- }
305
- function firstLetterIsLowercase(value) {
306
- const letter = value.match(/\p{L}/u)?.[0];
307
- return letter !== undefined && letter.toLocaleLowerCase() === letter && letter.toLocaleUpperCase() !== letter;
308
- }
309
- function ratio(count, total) {
310
- return total === 0 ? 0 : round(count / total);
311
- }
312
- function surfaceMetrics(messages) {
313
- const outgoing = messages.flatMap(({ message }) => message.retractedAt === null && message.direction === "outgoing" && message.kind === "text" && message.body !== null ? [message.body] : []);
314
- const characters = outgoing.map(characterCount);
315
- const words = outgoing.map(wordCount);
316
- return Object.freeze({
317
- outgoingTextMessages: outgoing.length,
318
- characters: numericDistribution(characters),
319
- words: numericDistribution(words),
320
- lowercaseStartsRatio: ratio(outgoing.filter(firstLetterIsLowercase).length, outgoing.length),
321
- terminalPunctuationRatio: ratio(outgoing.filter((body) => /[.!?\u2026\u3002\uFF01\uFF1F]$/u.test(body.trimEnd())).length, outgoing.length),
322
- questionRatio: ratio(outgoing.filter((body) => /[?\uFF1F]/u.test(body)).length, outgoing.length),
323
- exclamationRatio: ratio(outgoing.filter((body) => /[!\uFF01]/u.test(body)).length, outgoing.length),
324
- emojiMessageRatio: ratio(outgoing.filter((body) => /\p{Extended_Pictographic}/u.test(body)).length, outgoing.length),
325
- multilineRatio: ratio(outgoing.filter((body) => /\r?\n/u.test(body)).length, outgoing.length)
326
- });
327
- }
328
- function tempoMetrics(messages, responses) {
329
- const latencies = responses.map((response) => response.latencySeconds);
330
- const bundles = responses.map((response) => response.outgoingCount);
331
- const outgoingText = messages.filter(({ message }) => message.retractedAt === null && message.direction === "outgoing" && message.kind === "text" && message.body !== null);
332
- const replyEligible = outgoingText.filter(({ message }) => message.replyState !== "unavailable");
333
- const explicitReplies = replyEligible.filter(({ message }) => message.replyState === "explicit").length;
334
- const replyUnavailable = outgoingText.length - replyEligible.length;
335
- return Object.freeze({
336
- responseEpisodes: responses.length,
337
- responseLatencySeconds: Object.freeze({
338
- median: quantile(latencies, 0.5),
339
- p25: quantile(latencies, 0.25),
340
- p75: quantile(latencies, 0.75),
341
- p90: quantile(latencies, 0.9)
342
- }),
343
- outgoingMessagesPerResponse: Object.freeze({
344
- mean: bundles.length === 0 ? 0 : round(bundles.reduce((sum, value) => sum + value, 0) / bundles.length),
345
- median: quantile(bundles, 0.5) ?? 0,
346
- p90: quantile(bundles, 0.9) ?? 0,
347
- singleRatio: ratio(bundles.filter((value) => value === 1).length, bundles.length),
348
- multiRatio: ratio(bundles.filter((value) => value > 1).length, bundles.length)
349
- }),
350
- explicitReplyMessages: explicitReplies,
351
- explicitReplyEligibleMessages: replyEligible.length,
352
- explicitReplyUnavailableMessages: replyUnavailable,
353
- explicitReplyRatio: replyEligible.length === 0 ? null : ratio(explicitReplies, replyEligible.length),
354
- multiIncomingEpisodes: responses.filter((response) => response.incomingCount > 1).length,
355
- multiQuestionEpisodes: responses.filter((response) => response.incomingQuestions > 1).length
356
- });
357
- }
358
- function reactionMetrics(messages, facts) {
359
- const legacy = messages.filter(({ message }) => message.kind === "reaction" && message.retractedAt === null).map(({ message }) => ({
360
- id: message.id,
361
- externalId: message.sourceGuid,
362
- targetExternalId: message.replyToSourceGuid ?? message.sourceGuid,
363
- conversationId: message.conversationId,
364
- direction: message.direction,
365
- body: "unknown",
366
- reactedAt: message.sentAt,
367
- state: "active"
368
- }));
369
- const merged = new Map(legacy.map((fact) => [fact.id, fact]));
370
- for (const fact of facts ?? [])
371
- merged.set(fact.id, fact);
372
- const source = [...merged.values()];
373
- const ids = new Set;
374
- const reactions = source.filter((fact, index) => {
375
- if (typeof fact.id !== "string" || fact.id.length === 0 || ids.has(fact.id) || fact.direction !== null && fact.direction !== "incoming" && fact.direction !== "outgoing" || typeof fact.body !== "string" || fact.state !== "active" && fact.state !== "removed")
376
- throw new Error(`reactionFacts[${index}] is invalid`);
377
- if (fact.reactedAt !== null)
378
- canonicalTimestamp(fact.reactedAt, `reactionFacts[${index}].reactedAt`);
379
- ids.add(fact.id);
380
- return fact.state === "active";
381
- });
382
- const outgoing = reactions.filter(({ direction }) => direction === "outgoing").length;
383
- const incoming = reactions.filter(({ direction }) => direction === "incoming").length;
384
- const unknownDirection = reactions.length - outgoing - incoming;
385
- const outgoingActions = messages.filter(({ message }) => message.kind !== "reaction" && message.direction === "outgoing" && timelineEligible(message)).length + outgoing;
386
- return Object.freeze({
387
- total: reactions.length,
388
- incoming,
389
- outgoing,
390
- unknownDirection,
391
- dated: reactions.filter(({ reactedAt }) => reactedAt !== null).length,
392
- undated: reactions.filter(({ reactedAt }) => reactedAt === null).length,
393
- outgoingReactionRatio: ratio(outgoing, outgoingActions)
394
- });
395
- }
396
- function analyzeContact(messages, corpusRevision, contactId, options = {}) {
397
- if (typeof corpusRevision !== "string" || !/^[a-f0-9]{64}$/u.test(corpusRevision)) {
398
- throw new Error("corpusRevision must be a lowercase SHA-256 digest");
399
- }
400
- if (typeof contactId !== "string" || contactId.length < 1 || contactId.length > 256) {
401
- throw new Error("contactId must be bounded non-empty text");
402
- }
403
- const sessionGapSeconds = boundedGap(options.sessionGapSeconds, DEFAULT_SESSION_GAP_SECONDS, "sessionGapSeconds");
404
- const burstGapSeconds = boundedGap(options.burstGapSeconds, DEFAULT_BURST_GAP_SECONDS, "burstGapSeconds");
405
- if (burstGapSeconds > sessionGapSeconds) {
406
- throw new Error("burstGapSeconds cannot exceed sessionGapSeconds");
407
- }
408
- const ordered = orderedMessages(messages);
409
- const byConversation = new Map;
410
- for (const row of ordered) {
411
- const rows = byConversation.get(row.message.conversationId) ?? [];
412
- rows.push(row);
413
- byConversation.set(row.message.conversationId, rows);
414
- }
415
- const sessions = [];
416
- const burstRecords = [];
417
- const responses = [];
418
- for (const conversationId of [...byConversation.keys()].sort((left, right) => left.localeCompare(right, "en-US"))) {
419
- const rows = Object.freeze(byConversation.get(conversationId));
420
- const conversationSessions = sessionsFor(rows, corpusRevision, contactId, sessionGapSeconds);
421
- const conversationBursts = burstsFor(rows, conversationSessions, corpusRevision, contactId, burstGapSeconds);
422
- sessions.push(...conversationSessions);
423
- burstRecords.push(...conversationBursts);
424
- responses.push(...responsesFor(conversationBursts, corpusRevision, contactId));
425
- }
426
- sessions.sort((left, right) => left.startedAt.localeCompare(right.startedAt, "en-US") || left.id.localeCompare(right.id, "en-US"));
427
- burstRecords.sort((left, right) => left.metric.startedAt.localeCompare(right.metric.startedAt, "en-US") || left.metric.id.localeCompare(right.metric.id, "en-US"));
428
- responses.sort((left, right) => left.startedAt.localeCompare(right.startedAt, "en-US") || left.id.localeCompare(right.id, "en-US"));
429
- return Object.freeze({
430
- schemaVersion: METRICS_SCHEMA_VERSION,
431
- corpusRevision,
432
- contactId,
433
- firstMessageAt: ordered[0]?.message.sentAt ?? null,
434
- lastMessageAt: ordered.at(-1)?.message.sentAt ?? null,
435
- messageCount: ordered.length,
436
- incomingCount: ordered.filter(({ message }) => message.direction === "incoming").length,
437
- outgoingCount: ordered.filter(({ message }) => message.direction === "outgoing").length,
438
- textMessageCount: ordered.filter(({ message }) => message.retractedAt === null && message.kind === "text" && message.body !== null).length,
439
- sessionGapSeconds,
440
- burstGapSeconds,
441
- sessions: Object.freeze(sessions),
442
- bursts: Object.freeze(burstRecords.map(({ metric }) => metric)),
443
- responses: Object.freeze(responses),
444
- tempo: tempoMetrics(ordered, responses),
445
- reactions: reactionMetrics(ordered, options.reactionFacts),
446
- surface: surfaceMetrics(ordered)
447
- });
448
- }
449
92
  function studyMessages(response, byId, maximumTextBytes, maximumMessagesPerDirection) {
450
93
  const resolveRows = (ids, expectedDirection) => {
451
94
  const rows2 = [];
@@ -753,107 +396,6 @@ function buildStudyPacket(messages, metrics, options = {}) {
753
396
  })
754
397
  });
755
398
  }
756
- function buildEvaluationPackets(messages, metrics, options) {
757
- const limit = options.limit ?? 8;
758
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 25) {
759
- throw new Error("evaluation limit must be an integer from 1 through 25");
760
- }
761
- const maximumTextBytes = boundedStudyInteger(options.maxTextBytesPerMessage, DEFAULT_MAX_STUDY_TEXT_BYTES, MAX_STUDY_TEXT_BYTES, "maxTextBytesPerMessage");
762
- const maximumMessagesPerDirection = boundedStudyInteger(options.maxMessagesPerDirectionPerCase, DEFAULT_MAX_STUDY_MESSAGES_PER_DIRECTION, MAX_STUDY_MESSAGES_PER_DIRECTION, "maxMessagesPerDirectionPerCase");
763
- const maximumBodyBytes = boundedStudyInteger(options.maxTotalBodyBytes, DEFAULT_MAX_STUDY_PACKET_BODY_BYTES, MAX_STUDY_PACKET_BODY_BYTES, "maxTotalBodyBytes");
764
- const generatedAt = options.generatedAt ?? new Date().toISOString();
765
- canonicalTimestamp(generatedAt, "generatedAt");
766
- const evidenceRevision = options.evidenceRevision ?? metrics.corpusRevision;
767
- if (!/^[a-f0-9]{64}$/u.test(evidenceRevision)) {
768
- throw new Error("evidenceRevision must be a lowercase SHA-256 digest");
769
- }
770
- const after = optionalCanonicalTimestamp(options.after, "after");
771
- const before = optionalCanonicalTimestamp(options.before, "before");
772
- if (after.value === null || after.milliseconds === null)
773
- throw new Error("after is required");
774
- if (before.milliseconds !== null && after.milliseconds >= before.milliseconds)
775
- throw new Error("after must be earlier than before");
776
- const afterMilliseconds = after.milliseconds;
777
- const beforeMilliseconds = before.milliseconds;
778
- const ordered = orderedMessages(messages).filter(({ milliseconds }) => milliseconds >= afterMilliseconds && (beforeMilliseconds === null || milliseconds < beforeMilliseconds));
779
- const candidateSet = candidatesFor(ordered, metrics, maximumTextBytes, maximumMessagesPerDirection);
780
- const chronological = [...candidateSet.candidates].sort((left, right) => left.milliseconds - right.milliseconds || left.example.id.localeCompare(right.example.id, "en-US"));
781
- const selected = [];
782
- let emittedBodyBytes = 0;
783
- for (const candidate of chronological) {
784
- if (selected.length >= limit)
785
- break;
786
- if (candidate.bodyBytes > maximumBodyBytes - emittedBodyBytes)
787
- continue;
788
- selected.push(candidate);
789
- emittedBodyBytes += candidate.bodyBytes;
790
- }
791
- const caseIds = selected.map(({ example }) => example.id);
792
- const evaluationId = digest("evaluation", [
793
- metrics.corpusRevision,
794
- evidenceRevision,
795
- metrics.contactId,
796
- after.value,
797
- before.value ?? "",
798
- ...caseIds
799
- ]);
800
- const promptCases = Object.freeze(selected.map(({ example }) => Object.freeze({
801
- id: example.id,
802
- startedAt: example.startedAt,
803
- incoming: Object.freeze(example.messages.filter(({ direction }) => direction === "incoming"))
804
- })));
805
- const referenceCases = Object.freeze(selected.map(({ example }) => {
806
- const outgoing = Object.freeze(example.messages.filter(({ direction }) => direction === "outgoing"));
807
- return Object.freeze({
808
- id: example.id,
809
- startedAt: example.startedAt,
810
- outgoing,
811
- shape: Object.freeze({
812
- bubbles: outgoing.length,
813
- characters: outgoing.reduce((total, message) => total + characterCount(message.body), 0),
814
- words: outgoing.reduce((total, message) => total + wordCount(message.body), 0),
815
- explicitReplyMessages: outgoing.filter(({ explicitReply }) => explicitReply === true).length,
816
- explicitReplyEligibleMessages: outgoing.filter(({ explicitReply }) => explicitReply !== null).length,
817
- explicitReplyUnavailableMessages: outgoing.filter(({ explicitReply }) => explicitReply === null).length
818
- })
819
- });
820
- }));
821
- const promptMessages = promptCases.flatMap(({ incoming }) => incoming);
822
- const evidenceWindow = Object.freeze({ after: after.value, before: before.value });
823
- const shared = {
824
- schemaVersion: EVALUATION_PACKET_SCHEMA_VERSION,
825
- evaluationId,
826
- generatedAt,
827
- corpusRevision: metrics.corpusRevision,
828
- evidenceRevision,
829
- contactId: metrics.contactId,
830
- evidenceWindow
831
- };
832
- return Object.freeze({
833
- prompt: Object.freeze({
834
- ...shared,
835
- cases: promptCases,
836
- selection: Object.freeze({
837
- algorithm: "temporal-held-out-responses-v1",
838
- requestedLimit: limit,
839
- eligibleCandidates: candidateSet.candidates.length,
840
- emitted: promptCases.length
841
- }),
842
- budget: Object.freeze({
843
- maxTextBytesPerMessage: maximumTextBytes,
844
- maxMessagesPerDirectionPerCase: maximumMessagesPerDirection,
845
- maxTotalBodyBytes: maximumBodyBytes,
846
- emittedBodyBytes: promptMessages.reduce((total, message) => total + message.emittedBodyBytes, 0),
847
- truncatedMessages: promptMessages.filter(({ bodyTruncated }) => bodyTruncated).length
848
- })
849
- }),
850
- reference: Object.freeze({
851
- ...shared,
852
- cases: referenceCases,
853
- notice: "Open only after the candidate drafts for every case are fixed."
854
- })
855
- });
856
- }
857
399
 
858
400
  // src/ensoul-source-v1.ts
859
401
  var ENSOUL_SOURCE_PACKET_V1_SCHEMA_IDENTITY = "ensoul.source-packet.v1";
@@ -1075,4 +617,4 @@ function buildEnsoulMessagesSourcePacketV1(messages, metrics, options) {
1075
617
  });
1076
618
  }
1077
619
 
1078
- export { CORPUS_SCHEMA_VERSION, METRICS_SCHEMA_VERSION, PROFILE_SCHEMA_VERSION, LEGACY_PROFILE_SCHEMA_VERSION, STUDY_PACKET_SCHEMA_VERSION, EVALUATION_PACKET_SCHEMA_VERSION, CONTACTS_SCHEMA_VERSION, MESSAGE_BUNDLE_SCHEMA_VERSION, MESSAGE_BUNDLE_SCHEMA_VERSIONS, analyzeContact, buildStudyPacket, buildEvaluationPackets, ENSOUL_SOURCE_PACKET_V1_SCHEMA_IDENTITY, ENSOUL_MESSAGES_SOURCE_V1_ADAPTER_ID, ENSOUL_DIGEST_CANONICALIZATION, DEFAULT_ENSOUL_MESSAGES_EXAMPLE_LIMIT, MAX_ENSOUL_MESSAGES_EXAMPLE_LIMIT, ensoulSubjectMessages, ensoulSubjectReactions, buildEnsoulMessagesSourcePacketV1 };
620
+ export { CORPUS_SCHEMA_VERSION, METRICS_SCHEMA_VERSION, PROFILE_SCHEMA_VERSION, LEGACY_PROFILE_SCHEMA_VERSION, STUDY_PACKET_SCHEMA_VERSION, EVALUATION_PACKET_SCHEMA_VERSION, CONTACTS_SCHEMA_VERSION, MESSAGE_BUNDLE_SCHEMA_VERSION, MESSAGE_BUNDLE_SCHEMA_VERSIONS, ENSOUL_SOURCE_PACKET_V1_SCHEMA_IDENTITY, ENSOUL_MESSAGES_SOURCE_V1_ADAPTER_ID, ENSOUL_DIGEST_CANONICALIZATION, DEFAULT_ENSOUL_MESSAGES_EXAMPLE_LIMIT, MAX_ENSOUL_MESSAGES_EXAMPLE_LIMIT, ensoulSubjectMessages, ensoulSubjectReactions, buildEnsoulMessagesSourcePacketV1 };
@@ -12,9 +12,5 @@ function canonicalJson(value) {
12
12
  function sha256(value) {
13
13
  return createHash("sha256").update(value).digest("hex");
14
14
  }
15
- function prettyJson(value) {
16
- return `${JSON.stringify(value, null, 2)}
17
- `;
18
- }
19
15
 
20
- export { canonicalJson, sha256, prettyJson };
16
+ export { canonicalJson, sha256 };
package/dist/index.js CHANGED
@@ -17,13 +17,13 @@ import {
17
17
  buildEnsoulMessagesSourcePacketV1,
18
18
  ensoulSubjectMessages,
19
19
  ensoulSubjectReactions
20
- } from "./cli-x1qncxm7.js";
21
- import"./cli-qqafdvz9.js";
22
- import"./cli-kw20gkk3.js";
20
+ } from "./index-7dfvy41v.js";
21
+ import"./index-qqafdvz9.js";
22
+ import"./index-kw20gkk3.js";
23
23
  import {
24
24
  canonicalJson,
25
25
  sha256
26
- } from "./cli-ththzwja.js";
26
+ } from "./index-y1aec88m.js";
27
27
  export {
28
28
  sha256,
29
29
  ensoulSubjectReactions,