@czap/astro 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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/Satellite.d.ts +42 -0
  4. package/dist/Satellite.d.ts.map +1 -0
  5. package/dist/Satellite.js +55 -0
  6. package/dist/Satellite.js.map +1 -0
  7. package/dist/client-directives/gpu.d.ts +3 -0
  8. package/dist/client-directives/gpu.d.ts.map +1 -0
  9. package/dist/client-directives/gpu.js +5 -0
  10. package/dist/client-directives/gpu.js.map +1 -0
  11. package/dist/client-directives/llm.d.ts +3 -0
  12. package/dist/client-directives/llm.d.ts.map +1 -0
  13. package/dist/client-directives/llm.js +5 -0
  14. package/dist/client-directives/llm.js.map +1 -0
  15. package/dist/client-directives/satellite.d.ts +3 -0
  16. package/dist/client-directives/satellite.d.ts.map +1 -0
  17. package/dist/client-directives/satellite.js +5 -0
  18. package/dist/client-directives/satellite.js.map +1 -0
  19. package/dist/client-directives/stream.d.ts +3 -0
  20. package/dist/client-directives/stream.d.ts.map +1 -0
  21. package/dist/client-directives/stream.js +5 -0
  22. package/dist/client-directives/stream.js.map +1 -0
  23. package/dist/client-directives/wasm.d.ts +3 -0
  24. package/dist/client-directives/wasm.d.ts.map +1 -0
  25. package/dist/client-directives/wasm.js +6 -0
  26. package/dist/client-directives/wasm.js.map +1 -0
  27. package/dist/client-directives/worker.d.ts +3 -0
  28. package/dist/client-directives/worker.d.ts.map +1 -0
  29. package/dist/client-directives/worker.js +5 -0
  30. package/dist/client-directives/worker.js.map +1 -0
  31. package/dist/detect-upgrade.d.ts +16 -0
  32. package/dist/detect-upgrade.d.ts.map +1 -0
  33. package/dist/detect-upgrade.js +105 -0
  34. package/dist/detect-upgrade.js.map +1 -0
  35. package/dist/headers.d.ts +45 -0
  36. package/dist/headers.d.ts.map +1 -0
  37. package/dist/headers.js +64 -0
  38. package/dist/headers.js.map +1 -0
  39. package/dist/index.d.ts +30 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +26 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/integration.d.ts +76 -0
  44. package/dist/integration.d.ts.map +1 -0
  45. package/dist/integration.js +240 -0
  46. package/dist/integration.js.map +1 -0
  47. package/dist/middleware.d.ts +69 -0
  48. package/dist/middleware.d.ts.map +1 -0
  49. package/dist/middleware.js +75 -0
  50. package/dist/middleware.js.map +1 -0
  51. package/dist/quantize.d.ts +50 -0
  52. package/dist/quantize.d.ts.map +1 -0
  53. package/dist/quantize.js +122 -0
  54. package/dist/quantize.js.map +1 -0
  55. package/dist/runtime/boundary.d.ts +123 -0
  56. package/dist/runtime/boundary.d.ts.map +1 -0
  57. package/dist/runtime/boundary.js +164 -0
  58. package/dist/runtime/boundary.js.map +1 -0
  59. package/dist/runtime/globals.d.ts +32 -0
  60. package/dist/runtime/globals.d.ts.map +1 -0
  61. package/dist/runtime/globals.js +45 -0
  62. package/dist/runtime/globals.js.map +1 -0
  63. package/dist/runtime/gpu.d.ts +15 -0
  64. package/dist/runtime/gpu.d.ts.map +1 -0
  65. package/dist/runtime/gpu.js +266 -0
  66. package/dist/runtime/gpu.js.map +1 -0
  67. package/dist/runtime/index.d.ts +7 -0
  68. package/dist/runtime/index.d.ts.map +1 -0
  69. package/dist/runtime/index.js +5 -0
  70. package/dist/runtime/index.js.map +1 -0
  71. package/dist/runtime/llm-receipt-tracker.d.ts +21 -0
  72. package/dist/runtime/llm-receipt-tracker.d.ts.map +1 -0
  73. package/dist/runtime/llm-receipt-tracker.js +60 -0
  74. package/dist/runtime/llm-receipt-tracker.js.map +1 -0
  75. package/dist/runtime/llm-render-pipeline.d.ts +89 -0
  76. package/dist/runtime/llm-render-pipeline.d.ts.map +1 -0
  77. package/dist/runtime/llm-render-pipeline.js +241 -0
  78. package/dist/runtime/llm-render-pipeline.js.map +1 -0
  79. package/dist/runtime/llm-session.d.ts +126 -0
  80. package/dist/runtime/llm-session.d.ts.map +1 -0
  81. package/dist/runtime/llm-session.js +385 -0
  82. package/dist/runtime/llm-session.js.map +1 -0
  83. package/dist/runtime/llm.d.ts +16 -0
  84. package/dist/runtime/llm.d.ts.map +1 -0
  85. package/dist/runtime/llm.js +273 -0
  86. package/dist/runtime/llm.js.map +1 -0
  87. package/dist/runtime/policy.d.ts +100 -0
  88. package/dist/runtime/policy.d.ts.map +1 -0
  89. package/dist/runtime/policy.js +147 -0
  90. package/dist/runtime/policy.js.map +1 -0
  91. package/dist/runtime/receipt-chain.d.ts +22 -0
  92. package/dist/runtime/receipt-chain.d.ts.map +1 -0
  93. package/dist/runtime/receipt-chain.js +80 -0
  94. package/dist/runtime/receipt-chain.js.map +1 -0
  95. package/dist/runtime/runtime-session.d.ts +34 -0
  96. package/dist/runtime/runtime-session.d.ts.map +1 -0
  97. package/dist/runtime/runtime-session.js +102 -0
  98. package/dist/runtime/runtime-session.js.map +1 -0
  99. package/dist/runtime/satellite.d.ts +13 -0
  100. package/dist/runtime/satellite.d.ts.map +1 -0
  101. package/dist/runtime/satellite.js +59 -0
  102. package/dist/runtime/satellite.js.map +1 -0
  103. package/dist/runtime/slots.d.ts +34 -0
  104. package/dist/runtime/slots.d.ts.map +1 -0
  105. package/dist/runtime/slots.js +108 -0
  106. package/dist/runtime/slots.js.map +1 -0
  107. package/dist/runtime/stream-session.d.ts +47 -0
  108. package/dist/runtime/stream-session.d.ts.map +1 -0
  109. package/dist/runtime/stream-session.js +82 -0
  110. package/dist/runtime/stream-session.js.map +1 -0
  111. package/dist/runtime/stream.d.ts +9 -0
  112. package/dist/runtime/stream.d.ts.map +1 -0
  113. package/dist/runtime/stream.js +308 -0
  114. package/dist/runtime/stream.js.map +1 -0
  115. package/dist/runtime/url-policy.d.ts +28 -0
  116. package/dist/runtime/url-policy.d.ts.map +1 -0
  117. package/dist/runtime/url-policy.js +87 -0
  118. package/dist/runtime/url-policy.js.map +1 -0
  119. package/dist/runtime/wasm.d.ts +20 -0
  120. package/dist/runtime/wasm.d.ts.map +1 -0
  121. package/dist/runtime/wasm.js +70 -0
  122. package/dist/runtime/wasm.js.map +1 -0
  123. package/dist/runtime/worker.d.ts +11 -0
  124. package/dist/runtime/worker.d.ts.map +1 -0
  125. package/dist/runtime/worker.js +249 -0
  126. package/dist/runtime/worker.js.map +1 -0
  127. package/package.json +106 -0
  128. package/src/Satellite.astro +39 -0
  129. package/src/Satellite.ts +84 -0
  130. package/src/client-directives/gpu.ts +5 -0
  131. package/src/client-directives/llm.ts +5 -0
  132. package/src/client-directives/satellite.ts +5 -0
  133. package/src/client-directives/stream.ts +5 -0
  134. package/src/client-directives/wasm.ts +6 -0
  135. package/src/client-directives/worker.ts +5 -0
  136. package/src/detect-upgrade.ts +105 -0
  137. package/src/headers.ts +84 -0
  138. package/src/index.ts +30 -0
  139. package/src/integration.ts +309 -0
  140. package/src/middleware.ts +133 -0
  141. package/src/quantize.ts +173 -0
  142. package/src/runtime/boundary.ts +263 -0
  143. package/src/runtime/globals.ts +57 -0
  144. package/src/runtime/gpu.ts +291 -0
  145. package/src/runtime/index.ts +12 -0
  146. package/src/runtime/llm-receipt-tracker.ts +88 -0
  147. package/src/runtime/llm-render-pipeline.ts +366 -0
  148. package/src/runtime/llm-session.ts +548 -0
  149. package/src/runtime/llm.ts +344 -0
  150. package/src/runtime/policy.ts +229 -0
  151. package/src/runtime/receipt-chain.ts +106 -0
  152. package/src/runtime/runtime-session.ts +139 -0
  153. package/src/runtime/satellite.ts +80 -0
  154. package/src/runtime/slots.ts +136 -0
  155. package/src/runtime/stream-session.ts +125 -0
  156. package/src/runtime/stream.ts +407 -0
  157. package/src/runtime/url-policy.ts +107 -0
  158. package/src/runtime/wasm.ts +85 -0
  159. package/src/runtime/worker.ts +307 -0
@@ -0,0 +1,291 @@
1
+ import { Diagnostics, CANVAS_FALLBACK_WIDTH, CANVAS_FALLBACK_HEIGHT } from '@czap/core';
2
+ import { readRuntimeEndpointPolicy } from './policy.js';
3
+ import { allowRuntimeEndpointUrl } from './url-policy.js';
4
+
5
+ const DEFAULT_VERTEX_SHADER = `#version 300 es
6
+ precision mediump float;
7
+ in vec2 a_position;
8
+ out vec2 v_uv;
9
+ void main() {
10
+ v_uv = a_position * 0.5 + 0.5;
11
+ gl_Position = vec4(a_position, 0.0, 1.0);
12
+ }`;
13
+
14
+ const FULLSCREEN_QUAD = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]);
15
+
16
+ function compileShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader | null {
17
+ const shader = gl.createShader(type);
18
+ if (!shader) return null;
19
+ gl.shaderSource(shader, source);
20
+ gl.compileShader(shader);
21
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
22
+ Diagnostics.warn({
23
+ source: 'czap/astro.gpu',
24
+ code: 'shader-compile-failed',
25
+ message: 'Shader compilation failed.',
26
+ detail: gl.getShaderInfoLog(shader),
27
+ });
28
+ gl.deleteShader(shader);
29
+ return null;
30
+ }
31
+ return shader;
32
+ }
33
+
34
+ function createProgram(gl: WebGL2RenderingContext, vertSrc: string, fragSrc: string): WebGLProgram | null {
35
+ const vert = compileShader(gl, gl.VERTEX_SHADER, vertSrc);
36
+ const frag = compileShader(gl, gl.FRAGMENT_SHADER, fragSrc);
37
+ if (!vert || !frag) return null;
38
+
39
+ const program = gl.createProgram();
40
+ if (!program) return null;
41
+ gl.attachShader(program, vert);
42
+ gl.attachShader(program, frag);
43
+ gl.linkProgram(program);
44
+
45
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
46
+ Diagnostics.warn({
47
+ source: 'czap/astro.gpu',
48
+ code: 'program-link-failed',
49
+ message: 'Shader program linking failed.',
50
+ detail: gl.getProgramInfoLog(program),
51
+ });
52
+ gl.deleteProgram(program);
53
+ return null;
54
+ }
55
+
56
+ return program;
57
+ }
58
+
59
+ /**
60
+ * Entry point used by the `client:gpu` directive to wire a
61
+ * satellite element to a WebGL shader.
62
+ *
63
+ * Reads `data-czap-shader-type` / `data-czap-shader-src` off the
64
+ * element, fetches and compiles the program, then subscribes to
65
+ * `czap:uniform-update` events so each boundary transition updates the
66
+ * shader uniforms.
67
+ *
68
+ * @param load - Dynamic-import factory the directive passes in (kept
69
+ * async so the expensive GPU module is code-split).
70
+ * @param el - Satellite element carrying the shader attributes.
71
+ */
72
+ export function initGPUDirective(load: () => Promise<unknown>, el: HTMLElement): void {
73
+ const shaderType = el.getAttribute('data-czap-shader-type') ?? 'glsl';
74
+ const shaderSrc = allowRuntimeEndpointUrl(
75
+ el.getAttribute('data-czap-shader-src'),
76
+ 'gpu-shader',
77
+ 'czap/astro.gpu',
78
+ {
79
+ crossOriginRejected: 'shader-cross-origin-url-rejected',
80
+ malformedUrl: 'shader-malformed-url-rejected',
81
+ originNotAllowed: 'shader-origin-not-allowed',
82
+ endpointKindNotPermitted: 'shader-endpoint-kind-not-permitted',
83
+ },
84
+ readRuntimeEndpointPolicy(),
85
+ );
86
+
87
+ const tier = document.documentElement.getAttribute('data-czap-tier') ?? 'reactive';
88
+ if (tier === 'static' || tier === 'styled') {
89
+ load();
90
+ return;
91
+ }
92
+
93
+ if (shaderType === 'wgsl') {
94
+ Diagnostics.warnOnce({
95
+ source: 'czap/astro.gpu',
96
+ code: 'wgsl-not-yet-supported',
97
+ message:
98
+ 'WGSL shader directives are not yet wired into the Astro GPU runtime. Use WGSLCompiler directly for WGSL output.',
99
+ });
100
+ load();
101
+ return;
102
+ }
103
+
104
+ let canvas: HTMLCanvasElement;
105
+ if (el instanceof HTMLCanvasElement) {
106
+ canvas = el;
107
+ } else {
108
+ canvas = document.createElement('canvas');
109
+ canvas.width = el.clientWidth || CANVAS_FALLBACK_WIDTH;
110
+ canvas.height = el.clientHeight || CANVAS_FALLBACK_HEIGHT;
111
+ canvas.style.width = '100%';
112
+ canvas.style.height = '100%';
113
+ el.appendChild(canvas);
114
+ }
115
+
116
+ const gl = canvas.getContext('webgl2');
117
+ if (!gl) {
118
+ Diagnostics.warnOnce({
119
+ source: 'czap/astro.gpu',
120
+ code: 'webgl2-unavailable',
121
+ message: 'WebGL2 is unavailable; falling back to CSS rendering.',
122
+ });
123
+ load();
124
+ return;
125
+ }
126
+
127
+ const webgl = gl;
128
+
129
+ async function initShader(): Promise<void> {
130
+ let fragSource: string;
131
+
132
+ if (shaderSrc && (shaderSrc.startsWith('/') || shaderSrc.startsWith('http'))) {
133
+ try {
134
+ const response = await fetch(shaderSrc);
135
+ if (!response.ok) {
136
+ Diagnostics.warn({
137
+ source: 'czap/astro.gpu',
138
+ code: 'shader-fetch-failed',
139
+ message: 'Failed to fetch shader source.',
140
+ detail: response.statusText,
141
+ });
142
+ return;
143
+ }
144
+ fragSource = await response.text();
145
+ } catch (err) {
146
+ Diagnostics.warn({
147
+ source: 'czap/astro.gpu',
148
+ code: 'shader-fetch-threw',
149
+ message: 'Fetching shader source threw an error.',
150
+ cause: err,
151
+ });
152
+ return;
153
+ }
154
+ } else if (shaderSrc) {
155
+ fragSource = shaderSrc;
156
+ } else {
157
+ fragSource = `#version 300 es
158
+ precision mediump float;
159
+ in vec2 v_uv;
160
+ out vec4 fragColor;
161
+ uniform float u_state;
162
+ uniform float u_time;
163
+ void main() {
164
+ vec3 color = mix(vec3(0.2, 0.3, 0.8), vec3(0.8, 0.3, 0.2), u_state);
165
+ float bands = 4.0;
166
+ color = floor(color * bands) / bands;
167
+ fragColor = vec4(color, 1.0);
168
+ }`;
169
+ }
170
+
171
+ const program = createProgram(webgl, DEFAULT_VERTEX_SHADER, fragSource);
172
+ if (!program) return;
173
+
174
+ webgl.useProgram(program);
175
+
176
+ const vao = webgl.createVertexArray();
177
+ webgl.bindVertexArray(vao);
178
+ const buffer = webgl.createBuffer();
179
+ webgl.bindBuffer(webgl.ARRAY_BUFFER, buffer);
180
+ webgl.bufferData(webgl.ARRAY_BUFFER, FULLSCREEN_QUAD, webgl.STATIC_DRAW);
181
+ const posLoc = webgl.getAttribLocation(program, 'a_position');
182
+ webgl.enableVertexAttribArray(posLoc);
183
+ webgl.vertexAttribPointer(posLoc, 2, webgl.FLOAT, false, 0, 0);
184
+
185
+ const uniforms = new Map<string, WebGLUniformLocation>();
186
+ const numUniforms = webgl.getProgramParameter(program, webgl.ACTIVE_UNIFORMS);
187
+ for (let i = 0; i < numUniforms; i++) {
188
+ const info = webgl.getActiveUniform(program, i);
189
+ if (info) {
190
+ const loc = webgl.getUniformLocation(program, info.name);
191
+ if (loc) uniforms.set(info.name, loc);
192
+ }
193
+ }
194
+
195
+ const startTime = performance.now();
196
+ let animFrame = 0;
197
+
198
+ function render(): void {
199
+ const w = canvas.clientWidth;
200
+ const h = canvas.clientHeight;
201
+ if (canvas.width !== w || canvas.height !== h) {
202
+ canvas.width = w;
203
+ canvas.height = h;
204
+ webgl.viewport(0, 0, w, h);
205
+ }
206
+
207
+ const timeLoc = uniforms.get('u_time');
208
+ if (timeLoc) {
209
+ webgl.uniform1f(timeLoc, (performance.now() - startTime) / 1000);
210
+ }
211
+
212
+ const resLoc = uniforms.get('u_resolution');
213
+ if (resLoc) {
214
+ webgl.uniform2f(resLoc, w, h);
215
+ }
216
+
217
+ webgl.drawArrays(webgl.TRIANGLES, 0, 6);
218
+ animFrame = requestAnimationFrame(render);
219
+ }
220
+
221
+ const onElementUniformUpdate = (event: Event): void => {
222
+ /* v8 ignore next — `czap:uniform-update` is always dispatched via `new CustomEvent(...)`;
223
+ the guard narrows the generic `Event` parameter for TypeScript's typed `.detail` access. */
224
+ if (!(event instanceof CustomEvent)) return;
225
+ const detail = event.detail;
226
+ if (!detail) return;
227
+
228
+ const boundaryJson = el.getAttribute('data-czap-boundary');
229
+ if (boundaryJson && detail.discrete) {
230
+ try {
231
+ const boundary = JSON.parse(boundaryJson);
232
+ const stateName = detail.discrete[boundary.id ?? 'default'];
233
+ if (stateName) {
234
+ const idx = boundary.states.indexOf(stateName);
235
+ const stateLoc = uniforms.get('u_state');
236
+ if (stateLoc && idx >= 0) {
237
+ webgl.uniform1f(stateLoc, idx / Math.max(1, boundary.states.length - 1));
238
+ }
239
+ }
240
+ } catch {
241
+ Diagnostics.warnOnce({
242
+ source: 'czap/astro.gpu',
243
+ code: 'uniform-update-parse-failed',
244
+ message: 'Failed to parse boundary JSON during uniform update.',
245
+ });
246
+ }
247
+ }
248
+
249
+ if (detail.css) {
250
+ for (const [key, value] of Object.entries(detail.css)) {
251
+ const uniformName = key.replace('--czap-', 'u_').replace(/-/g, '_');
252
+ const loc = uniforms.get(uniformName);
253
+ if (loc && typeof value === 'string') {
254
+ const num = parseFloat(value);
255
+ if (!Number.isNaN(num)) {
256
+ webgl.uniform1f(loc, num);
257
+ }
258
+ }
259
+ }
260
+ }
261
+ };
262
+
263
+ const onDocumentUniformUpdate = (event: Event): void => {
264
+ /* v8 ignore next — `czap:uniform-update` is always dispatched via `new CustomEvent(...)`;
265
+ the guard narrows the generic `Event` parameter for TypeScript's typed `.detail` access. */
266
+ if (!(event instanceof CustomEvent)) return;
267
+ if (event.detail?.uniform && event.detail?.value !== undefined) {
268
+ const loc = uniforms.get(event.detail.uniform);
269
+ if (loc) {
270
+ webgl.uniform1f(loc, event.detail.value);
271
+ }
272
+ }
273
+ };
274
+
275
+ el.addEventListener('czap:uniform-update', onElementUniformUpdate);
276
+ document.addEventListener('czap:uniform-update', onDocumentUniformUpdate);
277
+
278
+ el.dispatchEvent(new CustomEvent('czap:gpu-ready', { bubbles: true }));
279
+ render();
280
+
281
+ el.addEventListener('czap:reinit', () => {
282
+ cancelAnimationFrame(animFrame);
283
+ el.removeEventListener('czap:uniform-update', onElementUniformUpdate);
284
+ document.removeEventListener('czap:uniform-update', onDocumentUniformUpdate);
285
+ webgl.deleteProgram(program);
286
+ });
287
+ }
288
+
289
+ void initShader();
290
+ load();
291
+ }
@@ -0,0 +1,12 @@
1
+ export { bootstrapSlots, getSlotRegistry, installSwapReinit, reinitializeDirectives, rescanSlots } from './slots.js';
2
+ export { configureWasmRuntime, loadWasmRuntime, resolveWasmUrl } from './wasm.js';
3
+ export { allowRuntimeEndpointUrl, allowSameOriginRuntimeUrl, isSameOriginRuntimeUrl } from './url-policy.js';
4
+ export {
5
+ configureRuntimePolicy,
6
+ normalizeRuntimeSecurityPolicy,
7
+ readRuntimeEndpointPolicy,
8
+ readRuntimeHtmlPolicy,
9
+ readRuntimePolicy,
10
+ } from './policy.js';
11
+ export type { RuntimeEndpointKind, RuntimeEndpointPolicy, HtmlPolicy } from '@czap/web';
12
+ export type { RuntimeHtmlPolicy, RuntimeSecurityPolicy, NormalizedRuntimeSecurityPolicy } from './policy.js';
@@ -0,0 +1,88 @@
1
+ import type { Receipt, UIFrame } from '@czap/core';
2
+ import { GenFrame } from '@czap/core';
3
+ import { createReceiptChain } from './receipt-chain.js';
4
+ import type { LLMRenderPipeline, LLMRenderHost } from './llm-render-pipeline.js';
5
+
6
+ /** Tracks receipt chain, pending frames, envelope ingestion, and gap replay. */
7
+ export interface LLMReceiptTracker {
8
+ readonly receiptChain: ReturnType<typeof createReceiptChain> | null;
9
+ readonly lastAckReceiptId: UIFrame['receiptId'] | null;
10
+
11
+ recordFrame(frame: UIFrame): void;
12
+ rememberEnvelope(envelope: Receipt.Envelope): void;
13
+ replayGap(pipeline: LLMRenderPipeline, host: LLMRenderHost): { readonly type: string };
14
+ reset(): void;
15
+ }
16
+
17
+ /**
18
+ * Build a fresh {@link LLMReceiptTracker}. Internally lazy-initialises
19
+ * the receipt chain on the first envelope or gap replay, so idle LLM
20
+ * sessions pay no storage cost.
21
+ */
22
+ export function createLLMReceiptTracker(): LLMReceiptTracker {
23
+ let _receiptChain: ReturnType<typeof createReceiptChain> | null = null;
24
+ let _pendingFrames: UIFrame[] | null = null;
25
+ let _lastAckReceiptId: UIFrame['receiptId'] | null = null;
26
+
27
+ function getReceiptChain(): ReturnType<typeof createReceiptChain> {
28
+ if (!_receiptChain) {
29
+ _receiptChain = createReceiptChain();
30
+ for (const frame of _pendingFrames ?? []) {
31
+ _receiptChain.rememberFrame(frame);
32
+ }
33
+ _pendingFrames = null;
34
+ }
35
+
36
+ return _receiptChain;
37
+ }
38
+
39
+ const tracker: LLMReceiptTracker = {
40
+ get receiptChain() {
41
+ return _receiptChain;
42
+ },
43
+ get lastAckReceiptId() {
44
+ return _lastAckReceiptId;
45
+ },
46
+
47
+ recordFrame(frame: UIFrame): void {
48
+ _lastAckReceiptId = frame.receiptId;
49
+ if (_receiptChain) {
50
+ _receiptChain.rememberFrame(frame);
51
+ return;
52
+ }
53
+
54
+ (_pendingFrames ??= []).push(frame);
55
+ },
56
+
57
+ rememberEnvelope(envelope: Receipt.Envelope): void {
58
+ getReceiptChain().ingestEnvelope(envelope);
59
+ },
60
+
61
+ replayGap(pipeline: LLMRenderPipeline, host: LLMRenderHost): { readonly type: string } {
62
+ pipeline.flushPendingText(host, tracker.recordFrame);
63
+ const strategy = GenFrame.resolveGap(
64
+ _lastAckReceiptId,
65
+ pipeline.llmRuntime?.tokenBuffer.length ?? 0,
66
+ getReceiptChain(),
67
+ {
68
+ canResume: false,
69
+ },
70
+ );
71
+ if (strategy.type === 'replay') {
72
+ for (const frame of strategy.frames) {
73
+ pipeline.renderFrame(frame, host);
74
+ }
75
+ }
76
+
77
+ return strategy;
78
+ },
79
+
80
+ reset(): void {
81
+ _lastAckReceiptId = null;
82
+ _receiptChain = null;
83
+ _pendingFrames = null;
84
+ },
85
+ };
86
+
87
+ return tracker;
88
+ }