@someonegg/codex-hub 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -2
  3. package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/dist/client/assets/index-Dx1LBBmn.js +304 -0
  63. package/dist/client/assets/index-lmwuA3r2.css +1 -0
  64. package/dist/client/index.html +16 -0
  65. package/dist/server/server/adapters/codex/agent-identity.js +9 -0
  66. package/dist/server/server/adapters/codex/catalog-source.js +2 -0
  67. package/dist/server/server/adapters/codex/codex-session-source.js +300 -0
  68. package/dist/server/server/adapters/codex/identity-resolver.js +107 -0
  69. package/dist/server/server/adapters/codex/interaction-parser.js +90 -0
  70. package/dist/server/server/adapters/codex/internal-event-parser.js +56 -0
  71. package/dist/server/server/adapters/codex/io-errors.js +5 -0
  72. package/dist/server/server/adapters/codex/jsonl-catalog-source.js +58 -0
  73. package/dist/server/server/adapters/codex/limits.js +10 -0
  74. package/dist/server/server/adapters/codex/message-normalizer.js +121 -0
  75. package/dist/server/server/adapters/codex/path-policy.js +88 -0
  76. package/dist/server/server/adapters/codex/project-directory-resolver.js +79 -0
  77. package/dist/server/server/adapters/codex/response-item-parser.js +100 -0
  78. package/dist/server/server/adapters/codex/rollout-decoder.js +286 -0
  79. package/dist/server/server/adapters/codex/session-index-reader.js +101 -0
  80. package/dist/server/server/adapters/codex/session-manager.js +62 -0
  81. package/dist/server/server/adapters/codex/session-normalizer.js +257 -0
  82. package/dist/server/server/adapters/codex/tool-normalizer.js +58 -0
  83. package/dist/server/server/adapters/codex/user-input-normalizer.js +123 -0
  84. package/dist/server/server/api/session-api-mapper.js +194 -0
  85. package/dist/server/server/application/session-read-service.js +109 -0
  86. package/dist/server/server/application/session-reader.js +2 -0
  87. package/dist/server/server/catalog/config.js +4 -0
  88. package/dist/server/server/catalog/session-catalog-service.js +103 -0
  89. package/dist/server/server/config.js +145 -0
  90. package/dist/server/server/create-hub.js +64 -0
  91. package/dist/server/server/domain/project-directory.js +5 -0
  92. package/dist/server/server/domain/session-domain.js +2 -0
  93. package/dist/server/server/domain/session-text.js +45 -0
  94. package/dist/server/server/http/api-route-handler.js +2 -0
  95. package/dist/server/server/http/api-router.js +63 -0
  96. package/dist/server/server/http/create-server.js +148 -0
  97. package/dist/server/server/http/query-params.js +19 -0
  98. package/dist/server/server/http/request-body.js +48 -0
  99. package/dist/server/server/http/router.js +16 -0
  100. package/dist/server/server/http/security.js +19 -0
  101. package/dist/server/server/http/session-interaction-routes.js +101 -0
  102. package/dist/server/server/http/session-management-routes.js +90 -0
  103. package/dist/server/server/http/session-path.js +13 -0
  104. package/dist/server/server/http/session-read-routes.js +198 -0
  105. package/dist/server/server/http/session-updates-routes.js +84 -0
  106. package/dist/server/server/http/terminal-preview-websocket.js +179 -0
  107. package/dist/server/server/http/upload-routes.js +74 -0
  108. package/dist/server/server/interaction/interaction-service.js +247 -0
  109. package/dist/server/server/interaction/message.js +19 -0
  110. package/dist/server/server/interaction/pane-binding-manager.js +60 -0
  111. package/dist/server/server/interaction/reconcile-session-state.js +21 -0
  112. package/dist/server/server/interaction/session-runtime.js +86 -0
  113. package/dist/server/server/interaction/terminal-preview-diff.js +71 -0
  114. package/dist/server/server/interaction/terminal-preview-service.js +194 -0
  115. package/dist/server/server/interaction/tmux-launcher.js +75 -0
  116. package/dist/server/server/interaction/tmux-preview-listener.js +101 -0
  117. package/dist/server/server/interaction/tmux-service.js +269 -0
  118. package/dist/server/server/interaction/transport.js +9 -0
  119. package/dist/server/server/main.js +41 -0
  120. package/dist/server/server/management/operation-service.js +540 -0
  121. package/dist/server/server/management/session-exit-service.js +50 -0
  122. package/dist/server/server/management/session-manager.js +59 -0
  123. package/dist/server/server/repository/catalog-snapshot-store.js +304 -0
  124. package/dist/server/server/repository/diagnostic-logger.js +70 -0
  125. package/dist/server/server/repository/opaque-cursor.js +79 -0
  126. package/dist/server/server/repository/refresh-coordinator.js +14 -0
  127. package/dist/server/server/repository/session-queries.js +151 -0
  128. package/dist/server/server/repository/session-query-criteria.js +2 -0
  129. package/dist/server/server/repository/timeline-prefix-index.js +342 -0
  130. package/dist/server/server/repository/timeline-prefix-registry.js +55 -0
  131. package/dist/server/server/security/opaque-id.js +13 -0
  132. package/dist/server/server/source/session-source.js +2 -0
  133. package/dist/server/server/updates/session-updates-service.js +147 -0
  134. package/dist/server/server/updates/update-revision.js +25 -0
  135. package/dist/server/shared/api-contract.js +2 -0
  136. package/dist/server/shared/domain.js +2 -0
  137. package/dist/server/shared/interaction.js +12 -0
  138. package/dist/server/shared/uploads.js +2 -0
  139. package/package.json +79 -4
@@ -0,0 +1,342 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ const PREFIX_BYTES = 24;
3
+ const PREFIX_PROTOCOL = "timeline-prefix-v2";
4
+ export function deriveTimelinePrefixIndex(normalized, prefixKey) {
5
+ const states = new Uint8Array((normalized.timeline.length + 1) * PREFIX_BYTES);
6
+ let previousOrdinal;
7
+ let itemIndex = 0;
8
+ let state = createHmac("sha256", prefixKey)
9
+ .update(PREFIX_PROTOCOL, "utf8")
10
+ .update("\0", "utf8")
11
+ .update(normalized.session.id, "utf8")
12
+ .digest()
13
+ .subarray(0, PREFIX_BYTES);
14
+ states.set(state, 0);
15
+ for (const item of normalized.timeline) {
16
+ validateOrdinal(item.ordinal, previousOrdinal);
17
+ previousOrdinal = item.ordinal;
18
+ itemIndex += 1;
19
+ state = advancePrefix(state, item, normalized, prefixKey);
20
+ states.set(state, itemIndex * PREFIX_BYTES);
21
+ }
22
+ return TimelinePrefixIndex.fromCompleteStates(itemIndex, states);
23
+ }
24
+ export function extendsTimelinePrefix(previous, next) {
25
+ if (previous.session.id !== next.session.id || previous.timeline.length > next.timeline.length) {
26
+ return false;
27
+ }
28
+ for (let index = 0; index < previous.timeline.length; index += 1) {
29
+ const item = previous.timeline[index];
30
+ if (item !== next.timeline[index])
31
+ return false;
32
+ if (item.kind === "tool" &&
33
+ previous.toolDetails.get(item.id) !== next.toolDetails.get(item.id)) {
34
+ return false;
35
+ }
36
+ if (item.kind === "directive" &&
37
+ previous.directiveDetails.get(item.id) !== next.directiveDetails.get(item.id)) {
38
+ return false;
39
+ }
40
+ if (item.kind === "internal" &&
41
+ previous.internalDetails.get(item.id) !== next.internalDetails.get(item.id)) {
42
+ return false;
43
+ }
44
+ }
45
+ return true;
46
+ }
47
+ export function extendTimelinePrefixIndex(previousIndex, previous, next, prefixKey,
48
+ /** Internal callers may skip a continuity scan they have just completed. */
49
+ continuityValidated = false) {
50
+ if (!continuityValidated && !extendsTimelinePrefix(previous, next)) {
51
+ throw new Error("Cannot extend a timeline prefix index across a changed prefix");
52
+ }
53
+ return TimelinePrefixIndex.fromAppend(previousIndex, previous, next, prefixKey);
54
+ }
55
+ export class TimelinePrefixIndex {
56
+ #itemCount;
57
+ #segments;
58
+ #segmentEnds;
59
+ constructor(itemCount, segments) {
60
+ this.#itemCount = itemCount;
61
+ this.#segments = segments;
62
+ const segmentEnds = [];
63
+ let storedSlots = 0;
64
+ for (const segment of segments) {
65
+ if (segment.byteLength === 0 || segment.byteLength % PREFIX_BYTES !== 0) {
66
+ throw new Error("Timeline prefix index has an invalid segment byte length");
67
+ }
68
+ storedSlots += segment.byteLength / PREFIX_BYTES;
69
+ segmentEnds.push(storedSlots);
70
+ }
71
+ if (storedSlots !== itemCount + 1) {
72
+ throw new Error("Timeline prefix index has an invalid byte length");
73
+ }
74
+ this.#segmentEnds = segmentEnds;
75
+ }
76
+ static fromCompleteStates(itemCount, states) {
77
+ return new TimelinePrefixIndex(itemCount, [states.slice()]);
78
+ }
79
+ static fromAppend(previous, previousNormalized, normalized, prefixKey) {
80
+ const previousItemCount = previousNormalized.timeline.length;
81
+ if (previous.#itemCount !== previousItemCount ||
82
+ normalized.timeline.length <= previousItemCount) {
83
+ throw new Error("Timeline prefix index does not match the append boundary");
84
+ }
85
+ const appendedCount = normalized.timeline.length - previousItemCount;
86
+ const appendedStates = new Uint8Array(appendedCount * PREFIX_BYTES);
87
+ let previousOrdinal = normalized.timeline[previousItemCount - 1]?.ordinal;
88
+ let state = previous.#slot(previousItemCount);
89
+ for (let itemIndex = previousItemCount; itemIndex < normalized.timeline.length; itemIndex += 1) {
90
+ const item = normalized.timeline[itemIndex];
91
+ validateOrdinal(item.ordinal, previousOrdinal);
92
+ previousOrdinal = item.ordinal;
93
+ state = advancePrefix(state, item, normalized, prefixKey);
94
+ appendedStates.set(state, (itemIndex - previousItemCount) * PREFIX_BYTES);
95
+ }
96
+ return new TimelinePrefixIndex(normalized.timeline.length, [
97
+ ...previous.#segments,
98
+ appendedStates,
99
+ ]);
100
+ }
101
+ get byteLength() {
102
+ return (this.#itemCount + 1) * PREFIX_BYTES;
103
+ }
104
+ boundaryAt(timeline, throughOrdinal) {
105
+ const maximumOrdinal = timeline.at(-1)?.ordinal ?? 0;
106
+ if (timeline.length !== this.#itemCount) {
107
+ throw new Error("Timeline prefix index does not match the timeline");
108
+ }
109
+ if (throughOrdinal > maximumOrdinal)
110
+ return null;
111
+ let low = 0;
112
+ let high = timeline.length;
113
+ while (low < high) {
114
+ const middle = low + Math.floor((high - low) / 2);
115
+ if (timeline[middle].ordinal <= throughOrdinal)
116
+ low = middle + 1;
117
+ else
118
+ high = middle;
119
+ }
120
+ return this.#boundary(timeline, low);
121
+ }
122
+ boundaryAtOrBefore(timeline, throughOrdinal) {
123
+ const maximumOrdinal = timeline.at(-1)?.ordinal ?? 0;
124
+ return this.boundaryAt(timeline, Math.min(throughOrdinal, maximumOrdinal));
125
+ }
126
+ matches(timeline, boundary, candidate) {
127
+ const encoded = Buffer.from(candidate, "base64url");
128
+ if (encoded.byteLength !== PREFIX_BYTES)
129
+ return false;
130
+ const slotIndex = boundary.throughOrdinal === 0 ? 0 : upperBound(timeline, boundary.throughOrdinal);
131
+ const slot = slotIndex === 0 || timeline[slotIndex - 1]?.ordinal === boundary.throughOrdinal
132
+ ? this.#slot(slotIndex)
133
+ : null;
134
+ return slot !== null && timingSafeEqual(slot, encoded);
135
+ }
136
+ #boundary(timeline, slot) {
137
+ return {
138
+ throughOrdinal: slot === 0 ? 0 : timeline[slot - 1].ordinal,
139
+ timelinePrefixRevision: Buffer.from(this.#slot(slot)).toString("base64url"),
140
+ };
141
+ }
142
+ #slot(index) {
143
+ let low = 0;
144
+ let high = this.#segmentEnds.length;
145
+ while (low < high) {
146
+ const middle = low + Math.floor((high - low) / 2);
147
+ if (this.#segmentEnds[middle] <= index)
148
+ low = middle + 1;
149
+ else
150
+ high = middle;
151
+ }
152
+ const segment = this.#segments[low];
153
+ if (segment === undefined) {
154
+ throw new Error("Timeline prefix slot is out of bounds");
155
+ }
156
+ const segmentStart = low === 0 ? 0 : this.#segmentEnds[low - 1];
157
+ const start = (index - segmentStart) * PREFIX_BYTES;
158
+ return segment.subarray(start, start + PREFIX_BYTES);
159
+ }
160
+ }
161
+ function validateOrdinal(ordinal, previousOrdinal) {
162
+ if (!Number.isSafeInteger(ordinal) ||
163
+ ordinal < 1 ||
164
+ (previousOrdinal !== undefined && ordinal <= previousOrdinal)) {
165
+ throw new Error("Timeline ordinals must be strictly increasing positive integers");
166
+ }
167
+ }
168
+ function advancePrefix(state, item, normalized, prefixKey) {
169
+ const chunks = [];
170
+ writeTimelineItem(new DigestWriter((chunk) => {
171
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : Buffer.from(chunk));
172
+ }), item, normalized);
173
+ return createHmac("sha256", prefixKey)
174
+ .update(state)
175
+ .update(Buffer.concat(chunks))
176
+ .digest()
177
+ .subarray(0, PREFIX_BYTES);
178
+ }
179
+ function upperBound(timeline, ordinal) {
180
+ let low = 0;
181
+ let high = timeline.length;
182
+ while (low < high) {
183
+ const middle = low + Math.floor((high - low) / 2);
184
+ if (timeline[middle].ordinal <= ordinal)
185
+ low = middle + 1;
186
+ else
187
+ high = middle;
188
+ }
189
+ return low;
190
+ }
191
+ function writeTimelineItem(writer, item, normalized) {
192
+ writer.string("kind", item.kind);
193
+ writer.string("id", item.id);
194
+ writer.number("ordinal", item.ordinal);
195
+ writer.nullableString("timestamp", item.timestamp);
196
+ switch (item.kind) {
197
+ case "message":
198
+ writer.string("role", item.role);
199
+ writer.nullableString("phase", item.phase);
200
+ writer.nullableString("itemType", item.itemType ?? null);
201
+ writer.string("markdown", item.markdown);
202
+ return;
203
+ case "directive":
204
+ writer.boolean("hasDetail", item.hasDetail);
205
+ if (!item.hasDetail) {
206
+ writer.string("text", item.text);
207
+ return;
208
+ }
209
+ writer.string("summary", item.summary);
210
+ writer.number("charCount", item.charCount);
211
+ writer.nullableObject("detail", normalized.directiveDetails.get(item.id) ?? null, (entry, detail) => {
212
+ entry.string("text", detail.text);
213
+ entry.boolean("truncated", detail.truncated);
214
+ });
215
+ return;
216
+ case "tool":
217
+ writer.string("stage", item.stage);
218
+ writer.string("callId", item.callId);
219
+ writer.string("toolName", item.toolName);
220
+ if (item.stage === "output")
221
+ writer.string("status", item.status);
222
+ writer.nullableString("preview", item.preview);
223
+ writer.number("charCount", item.charCount);
224
+ writer.boolean("hasDetail", item.hasDetail);
225
+ writer.nullableObject("detail", normalized.toolDetails.get(item.id) ?? null, (entry, detail) => {
226
+ entry.nullableString("input", detail.input);
227
+ entry.nullableString("output", detail.output);
228
+ entry.boolean("truncated", detail.truncated);
229
+ });
230
+ return;
231
+ case "user_input":
232
+ writer.string("stage", item.stage);
233
+ writer.string("callId", item.callId);
234
+ if (item.stage === "request") {
235
+ writer.array("questions", item.questions, (entry, question) => {
236
+ entry.string("id", question.id);
237
+ entry.string("header", question.header);
238
+ entry.string("question", question.question);
239
+ entry.array("options", question.options, (optionEntry, option) => {
240
+ optionEntry.string("label", option.label);
241
+ optionEntry.string("description", option.description);
242
+ });
243
+ });
244
+ return;
245
+ }
246
+ writer.string("outcome", item.outcome);
247
+ if (item.outcome === "answered") {
248
+ writer.array("answers", item.answers, (entry, answer) => {
249
+ entry.string("questionId", answer.questionId);
250
+ entry.array("answers", answer.answers, (answerEntry, value) => {
251
+ answerEntry.valueString(value);
252
+ });
253
+ });
254
+ }
255
+ else if (item.outcome === "unavailable") {
256
+ writer.string("summary", item.summary);
257
+ }
258
+ return;
259
+ case "token":
260
+ writer.nullableObject("tokenUsage.total", item.tokenUsage.total, writeTokenCounters);
261
+ writer.nullableObject("tokenUsage.last", item.tokenUsage.last, writeTokenCounters);
262
+ return;
263
+ case "internal":
264
+ writer.string("eventType", item.eventType);
265
+ writer.nullableObject("detail", normalized.internalDetails.get(item.id) ?? null, (entry, detail) => {
266
+ entry.string("json", detail.json);
267
+ entry.boolean("truncated", detail.truncated);
268
+ });
269
+ return;
270
+ }
271
+ }
272
+ function writeTokenCounters(writer, counters) {
273
+ writer.nullableNumber("totalTokens", counters.totalTokens);
274
+ writer.nullableNumber("inputTokens", counters.inputTokens);
275
+ writer.nullableNumber("cachedInputTokens", counters.cachedInputTokens);
276
+ writer.nullableNumber("cacheWriteInputTokens", counters.cacheWriteInputTokens);
277
+ writer.nullableNumber("outputTokens", counters.outputTokens);
278
+ writer.nullableNumber("reasoningOutputTokens", counters.reasoningOutputTokens);
279
+ }
280
+ class DigestWriter {
281
+ update;
282
+ constructor(update) {
283
+ this.update = update;
284
+ }
285
+ string(name, value) {
286
+ this.#field(name, "string");
287
+ this.valueString(value);
288
+ }
289
+ nullableString(name, value) {
290
+ this.#field(name, "nullable-string");
291
+ if (value === null) {
292
+ this.#token("null");
293
+ return;
294
+ }
295
+ this.valueString(value);
296
+ }
297
+ boolean(name, value) {
298
+ this.#field(name, "boolean");
299
+ this.#token(value ? "true" : "false");
300
+ }
301
+ number(name, value) {
302
+ this.#field(name, "number");
303
+ this.#token(String(value));
304
+ }
305
+ nullableNumber(name, value) {
306
+ this.#field(name, "nullable-number");
307
+ this.#token(value === null ? "null" : String(value));
308
+ }
309
+ nullableObject(name, value, write) {
310
+ this.#field(name, "nullable-object");
311
+ if (value === null) {
312
+ this.#token("null");
313
+ return;
314
+ }
315
+ this.#token("object-start");
316
+ write(this, value);
317
+ this.#token("object-end");
318
+ }
319
+ array(name, values, write) {
320
+ this.#field(name, "array");
321
+ this.#token(String(values.length));
322
+ for (const value of values) {
323
+ this.#token("item-start");
324
+ write(this, value);
325
+ this.#token("item-end");
326
+ }
327
+ }
328
+ valueString(value) {
329
+ const bytes = Buffer.byteLength(value, "utf8");
330
+ this.update(`value:${bytes}:`);
331
+ this.update(value);
332
+ this.update(";");
333
+ }
334
+ #field(name, type) {
335
+ this.#token(`field:${type}`);
336
+ this.valueString(name);
337
+ }
338
+ #token(value) {
339
+ this.update(`${value.length}:${value};`);
340
+ }
341
+ }
342
+ //# sourceMappingURL=timeline-prefix-index.js.map
@@ -0,0 +1,55 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { deriveTimelinePrefixIndex, extendsTimelinePrefix, extendTimelinePrefixIndex, } from "./timeline-prefix-index.js";
3
+ function buildTimelinePrefixIndex(normalized, prefixKey, previous) {
4
+ return previous !== undefined &&
5
+ extendsTimelinePrefix(previous.normalized, normalized) &&
6
+ previous.normalized.timeline.length < normalized.timeline.length
7
+ ? extendTimelinePrefixIndex(previous.timelinePrefixIndex, previous.normalized, normalized, prefixKey, true)
8
+ : deriveTimelinePrefixIndex(normalized, prefixKey);
9
+ }
10
+ export class TimelinePrefixRegistry {
11
+ buildIndex;
12
+ prefixKey;
13
+ #published = new Map();
14
+ #generation = 0n;
15
+ constructor(buildIndex = buildTimelinePrefixIndex, prefixKey = randomBytes(32)) {
16
+ this.buildIndex = buildIndex;
17
+ this.prefixKey = prefixKey;
18
+ }
19
+ prepare(sessions, dirtyIds) {
20
+ const baseGeneration = this.#generation;
21
+ const next = new Map();
22
+ for (const [id, normalized] of sessions) {
23
+ const previous = this.#published.get(id);
24
+ if (previous !== undefined && dirtyIds !== undefined && !dirtyIds.has(id)) {
25
+ next.set(id, previous);
26
+ continue;
27
+ }
28
+ const unchangedPrefix = previous !== undefined &&
29
+ previous.normalized.timeline.length === normalized.timeline.length &&
30
+ extendsTimelinePrefix(previous.normalized, normalized);
31
+ const published = {
32
+ normalized,
33
+ timelinePrefixIndex: unchangedPrefix
34
+ ? previous.timelinePrefixIndex
35
+ : this.buildIndex(normalized, this.prefixKey, previous),
36
+ };
37
+ next.set(id, published);
38
+ }
39
+ let committed = false;
40
+ return {
41
+ sessions: next,
42
+ commit: () => {
43
+ if (committed)
44
+ return;
45
+ if (this.#generation !== baseGeneration) {
46
+ throw new Error("Cannot commit stale prepared timeline prefixes");
47
+ }
48
+ this.#published = next;
49
+ this.#generation += 1n;
50
+ committed = true;
51
+ },
52
+ };
53
+ }
54
+ }
55
+ //# sourceMappingURL=timeline-prefix-registry.js.map
@@ -0,0 +1,13 @@
1
+ import { createHash } from "node:crypto";
2
+ export function encodeStringTuple(...parts) {
3
+ return parts.map((part) => `${part.length}:${part}`).join("");
4
+ }
5
+ export function opaqueIdForParts(...parts) {
6
+ return createHash("sha256")
7
+ .update(encodeStringTuple(...parts), "utf16le")
8
+ .digest("base64url");
9
+ }
10
+ export function opaqueIdForPath(canonicalPath) {
11
+ return createHash("sha256").update(canonicalPath, "utf8").digest("base64url");
12
+ }
13
+ //# sourceMappingURL=opaque-id.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-source.js.map
@@ -0,0 +1,147 @@
1
+ import { reconcileSessionState } from "../interaction/reconcile-session-state.js";
2
+ import { createProcessUpdateRevisionFactory, withUpdateRevision, } from "./update-revision.js";
3
+ export const UPDATES_PROBE_INTERVAL_MS = 1_500;
4
+ export const UPDATES_WAIT_TIMEOUT_MS = 25_000;
5
+ export const MAX_UPDATES_WAITERS = 100;
6
+ export const MAX_UPDATES_WAITERS_PER_SESSION = 10;
7
+ export class SessionUpdatesError extends Error {
8
+ code;
9
+ constructor(code, message) {
10
+ super(message);
11
+ this.code = code;
12
+ this.name = "SessionUpdatesError";
13
+ }
14
+ }
15
+ export class SessionUpdatesService {
16
+ sessions;
17
+ options;
18
+ #createRevision;
19
+ #probeIntervalMs;
20
+ #waitTimeoutMs;
21
+ #maxWaiters;
22
+ #maxWaitersPerSession;
23
+ #now;
24
+ #waiters = new Map();
25
+ #waitersBySession = new Map();
26
+ #closed = false;
27
+ constructor(sessions, options = {}) {
28
+ this.sessions = sessions;
29
+ this.options = options;
30
+ this.#createRevision = options.createRevision ?? createProcessUpdateRevisionFactory();
31
+ this.#probeIntervalMs = options.probeIntervalMs ?? UPDATES_PROBE_INTERVAL_MS;
32
+ this.#waitTimeoutMs = options.waitTimeoutMs ?? UPDATES_WAIT_TIMEOUT_MS;
33
+ this.#maxWaiters = options.maxWaiters ?? MAX_UPDATES_WAITERS;
34
+ this.#maxWaitersPerSession = options.maxWaitersPerSession ?? MAX_UPDATES_WAITERS_PER_SESSION;
35
+ this.#now = options.now ?? Date.now;
36
+ }
37
+ revision(session, interaction) {
38
+ return this.#createRevision(session, interaction);
39
+ }
40
+ async wait(sessionId, query, signal) {
41
+ this.#throwIfAborted(signal);
42
+ const initial = await this.#probe(sessionId, query);
43
+ if (initial.updateRevision !== query.after || initial.hasMore)
44
+ return initial;
45
+ if (initial.session.status === "archived")
46
+ return null;
47
+ const waiter = this.#acquire(sessionId, signal);
48
+ const deadline = this.#now() + this.#waitTimeoutMs;
49
+ try {
50
+ while (true) {
51
+ const remaining = deadline - this.#now();
52
+ if (remaining <= 0)
53
+ return null;
54
+ await delay(Math.min(this.#probeIntervalMs, remaining), waiter.signal);
55
+ const current = await this.#probe(sessionId, query);
56
+ if (current.updateRevision !== query.after || current.hasMore)
57
+ return current;
58
+ }
59
+ }
60
+ finally {
61
+ this.#release(waiter);
62
+ }
63
+ }
64
+ close() {
65
+ if (this.#closed)
66
+ return;
67
+ this.#closed = true;
68
+ for (const waiter of this.#waiters.keys())
69
+ waiter.abort();
70
+ }
71
+ async #probe(sessionId, query) {
72
+ this.#throwIfAborted();
73
+ const snapshot = await this.sessions.getSessionUpdates(sessionId, query.cursor);
74
+ if (snapshot === null) {
75
+ throw new SessionUpdatesError("session_not_found", "Session not found");
76
+ }
77
+ const interaction = await this.#describeInteraction(sessionId, snapshot.interactionSession);
78
+ return withUpdateRevision(reconcileSessionState({
79
+ session: snapshot.session,
80
+ cursor: snapshot.cursor,
81
+ hasMore: snapshot.hasMore,
82
+ interaction,
83
+ }), this.#createRevision);
84
+ }
85
+ #describeInteraction(sessionId, input) {
86
+ if (this.options.interaction === undefined)
87
+ return Promise.resolve({ supported: false, attachCommand: null });
88
+ return this.options.interaction.describeSnapshot(sessionId, input);
89
+ }
90
+ #acquire(sessionId, signal) {
91
+ this.#throwIfAborted(signal);
92
+ const sessionCount = this.#waitersBySession.get(sessionId) ?? 0;
93
+ if (this.#waiters.size >= this.#maxWaiters || sessionCount >= this.#maxWaitersPerSession) {
94
+ throw new SessionUpdatesError("updates_capacity_exceeded", "Too many automatic update requests are already waiting");
95
+ }
96
+ const controller = new AbortController();
97
+ const abort = () => controller.abort(signal?.reason);
98
+ signal?.addEventListener("abort", abort, { once: true });
99
+ if (signal?.aborted)
100
+ controller.abort(signal.reason);
101
+ this.#waiters.set(controller, {
102
+ sessionId,
103
+ externalSignal: signal,
104
+ externalAbort: abort,
105
+ });
106
+ this.#waitersBySession.set(sessionId, sessionCount + 1);
107
+ return controller;
108
+ }
109
+ #release(controller) {
110
+ const entry = this.#waiters.get(controller);
111
+ if (entry === undefined)
112
+ return;
113
+ this.#waiters.delete(controller);
114
+ const { sessionId } = entry;
115
+ const count = (this.#waitersBySession.get(sessionId) ?? 1) - 1;
116
+ if (count === 0)
117
+ this.#waitersBySession.delete(sessionId);
118
+ else
119
+ this.#waitersBySession.set(sessionId, count);
120
+ entry.externalSignal?.removeEventListener("abort", entry.externalAbort);
121
+ }
122
+ #throwIfAborted(signal) {
123
+ if (this.#closed || signal?.aborted)
124
+ throw abortError();
125
+ }
126
+ }
127
+ function delay(milliseconds, signal) {
128
+ return new Promise((resolve, reject) => {
129
+ if (signal.aborted)
130
+ return reject(abortError());
131
+ const timer = setTimeout(done, milliseconds);
132
+ const abort = () => {
133
+ clearTimeout(timer);
134
+ signal.removeEventListener("abort", abort);
135
+ reject(abortError());
136
+ };
137
+ function done() {
138
+ signal.removeEventListener("abort", abort);
139
+ resolve();
140
+ }
141
+ signal.addEventListener("abort", abort, { once: true });
142
+ });
143
+ }
144
+ function abortError() {
145
+ return new DOMException("The operation was aborted", "AbortError");
146
+ }
147
+ //# sourceMappingURL=session-updates-service.js.map
@@ -0,0 +1,25 @@
1
+ import { createHmac, randomBytes } from "node:crypto";
2
+ export function createProcessUpdateRevisionFactory(secret = randomBytes(32)) {
3
+ return (session, interaction) => createHmac("sha256", secret)
4
+ .update(canonicalJson({ session, interaction }))
5
+ .digest("base64url");
6
+ }
7
+ export function withUpdateRevision(value, createRevision) {
8
+ const { nickname: _nickname, ...revisionSession } = value.session;
9
+ return {
10
+ ...value,
11
+ updateRevision: createRevision(revisionSession, value.interaction),
12
+ };
13
+ }
14
+ function canonicalJson(value) {
15
+ if (Array.isArray(value))
16
+ return `[${value.map(canonicalJson).join(",")}]`;
17
+ if (value !== null && typeof value === "object") {
18
+ return `{${Object.entries(value)
19
+ .sort(([left], [right]) => left.localeCompare(right))
20
+ .map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`)
21
+ .join(",")}}`;
22
+ }
23
+ return JSON.stringify(value);
24
+ }
25
+ //# sourceMappingURL=update-revision.js.map
@@ -0,0 +1,2 @@
1
+ export * from "./interaction.js";
2
+ //# sourceMappingURL=api-contract.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=domain.js.map
@@ -0,0 +1,12 @@
1
+ export const MAX_INTERACTION_MESSAGE_BYTES = 64 * 1024;
2
+ export const MAX_INTERACTION_KEY_SEQUENCE_LENGTH = 64;
3
+ export const INTERACTION_KEYS = [
4
+ "enter",
5
+ "up",
6
+ "down",
7
+ "left",
8
+ "right",
9
+ "interrupt",
10
+ "plan",
11
+ ];
12
+ //# sourceMappingURL=interaction.js.map
@@ -0,0 +1,2 @@
1
+ export const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
2
+ //# sourceMappingURL=uploads.js.map