-
-
Notifications
You must be signed in to change notification settings - Fork 119
Expand file tree
/
Copy pathmessage-ids.ts
More file actions
133 lines (114 loc) · 3.79 KB
/
message-ids.ts
File metadata and controls
133 lines (114 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import type { SessionState, WithParts } from "./state"
const MESSAGE_REF_REGEX = /^m(\d{4})$/
const BLOCK_REF_REGEX = /^b([1-9]\d*)$/
const MESSAGE_ID_TAG_NAME = "dcp-message-id"
const MESSAGE_REF_WIDTH = 4
const MESSAGE_REF_MIN_INDEX = 0
export const MESSAGE_REF_MAX_INDEX = 9999
export type ParsedBoundaryId =
| {
kind: "message"
ref: string
index: number
}
| {
kind: "compressed-block"
ref: string
blockId: number
}
export function formatMessageRef(index: number): string {
if (
!Number.isInteger(index) ||
index < MESSAGE_REF_MIN_INDEX ||
index > MESSAGE_REF_MAX_INDEX
) {
throw new Error(
`Message ID index out of bounds: ${index}. Supported range is 0-${MESSAGE_REF_MAX_INDEX}.`,
)
}
return `m${index.toString().padStart(MESSAGE_REF_WIDTH, "0")}`
}
export function formatBlockRef(blockId: number): string {
if (!Number.isInteger(blockId) || blockId < 1) {
throw new Error(`Invalid block ID: ${blockId}`)
}
return `b${blockId}`
}
export function parseMessageRef(ref: string): number | null {
const normalized = ref.trim().toLowerCase()
const match = normalized.match(MESSAGE_REF_REGEX)
if (!match) {
return null
}
const index = Number.parseInt(match[1], 10)
return Number.isInteger(index) ? index : null
}
export function parseBlockRef(ref: string): number | null {
const normalized = ref.trim().toLowerCase()
const match = normalized.match(BLOCK_REF_REGEX)
if (!match) {
return null
}
const id = Number.parseInt(match[1], 10)
return Number.isInteger(id) ? id : null
}
export function parseBoundaryId(id: string): ParsedBoundaryId | null {
const normalized = id.trim().toLowerCase()
const messageIndex = parseMessageRef(normalized)
if (messageIndex !== null) {
return {
kind: "message",
ref: formatMessageRef(messageIndex),
index: messageIndex,
}
}
const blockId = parseBlockRef(normalized)
if (blockId !== null) {
return {
kind: "compressed-block",
ref: formatBlockRef(blockId),
blockId,
}
}
return null
}
export function formatMessageIdTag(ref: string): string {
return `\n<${MESSAGE_ID_TAG_NAME}>${ref}</${MESSAGE_ID_TAG_NAME}>`
}
export function assignMessageRefs(state: SessionState, messages: WithParts[]): number {
let assigned = 0
for (const message of messages) {
const rawMessageId = message.info.id
if (typeof rawMessageId !== "string" || rawMessageId.length === 0) {
continue
}
const existingRef = state.messageIds.byRawId.get(rawMessageId)
if (existingRef) {
if (state.messageIds.byRef.get(existingRef) !== rawMessageId) {
state.messageIds.byRef.set(existingRef, rawMessageId)
}
continue
}
const ref = allocateNextMessageRef(state)
state.messageIds.byRawId.set(rawMessageId, ref)
state.messageIds.byRef.set(ref, rawMessageId)
assigned++
}
return assigned
}
function allocateNextMessageRef(state: SessionState): string {
let candidate = Number.isInteger(state.messageIds.nextRef)
? Math.max(MESSAGE_REF_MIN_INDEX, state.messageIds.nextRef)
: MESSAGE_REF_MIN_INDEX
while (candidate <= MESSAGE_REF_MAX_INDEX) {
const ref = formatMessageRef(candidate)
if (!state.messageIds.byRef.has(ref)) {
state.messageIds.nextRef = candidate + 1
return ref
}
candidate++
}
throw new Error(
`Message ID alias capacity exceeded. Cannot allocate more than ${formatMessageRef(MESSAGE_REF_MAX_INDEX)} aliases in this session.`,
)
}