@lazyingart/agent-web 0.1.40
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.
- package/LICENSE +22 -0
- package/README.md +438 -0
- package/docs/architecture.md +503 -0
- package/package.json +43 -0
- package/src/aginti-adapter.js +602 -0
- package/src/chat-context.js +1020 -0
- package/src/chat-migrations.js +947 -0
- package/src/chat-store.js +3308 -0
- package/src/cli.js +134 -0
- package/src/cloud-server.js +2043 -0
- package/src/contracts.js +103 -0
- package/src/deterministic-context-summarizer.js +254 -0
- package/src/direct-chat-capability-limits.js +66 -0
- package/src/direct-chat-contract.js +3 -0
- package/src/errors.js +50 -0
- package/src/http-contract.js +592 -0
- package/src/index.js +88 -0
- package/src/localllm-connector.js +667 -0
- package/src/migrations.js +231 -0
- package/src/operator-health.js +184 -0
- package/src/password-verifier.js +131 -0
- package/src/service-config.js +547 -0
- package/src/service.js +408 -0
- package/src/sqlite-health.js +83 -0
- package/src/storage-path.js +130 -0
- package/src/store.js +914 -0
- package/src/validation.js +181 -0
- package/src/vision-attachment.js +404 -0
- package/src/web/aginti-client.js +552 -0
- package/src/web/aginti-protocol.js +1146 -0
- package/src/web/asset-map.js +462 -0
- package/src/web/browser-app.js +6491 -0
- package/src/web/cloud-session-client.js +427 -0
- package/src/web/direct-chat-client.js +1482 -0
- package/src/web/index.js +10 -0
- package/src/web/presentation-state.js +107 -0
- package/src/web/pwa-assets.js +854 -0
- package/src/web/pwa-update-handoff-store.js +179 -0
- package/src/web/safe-rendering.js +836 -0
- package/src/web/vision-image-client.js +546 -0
- package/src/web/vision-image-sanitizer.js +168 -0
- package/src/web/web-release.js +28 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const PNG_SIGNATURE = Object.freeze([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
2
|
+
const PNG_METADATA_CHUNKS = new Set(["caBX", "eXIf", "iCCP", "iTXt", "tEXt", "zTXt"]);
|
|
3
|
+
const JPEG_METADATA_MARKERS = new Set([
|
|
4
|
+
0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8,
|
|
5
|
+
0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfe,
|
|
6
|
+
]);
|
|
7
|
+
const SANITIZER_BYTE_LIMIT = 8 * 1024 * 1024;
|
|
8
|
+
const CRC32_TABLE = (() => {
|
|
9
|
+
const table = new Uint32Array(256);
|
|
10
|
+
for (let index = 0; index < table.length; index += 1) {
|
|
11
|
+
let value = index;
|
|
12
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
13
|
+
value = (value >>> 1) ^ (0xedb88320 & -(value & 1));
|
|
14
|
+
}
|
|
15
|
+
table[index] = value >>> 0;
|
|
16
|
+
}
|
|
17
|
+
return table;
|
|
18
|
+
})();
|
|
19
|
+
|
|
20
|
+
function fail(message) {
|
|
21
|
+
throw new TypeError(message);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function joinedBytes(parts, byteLength) {
|
|
25
|
+
const result = new Uint8Array(byteLength);
|
|
26
|
+
let offset = 0;
|
|
27
|
+
for (const part of parts) {
|
|
28
|
+
result.set(part, offset);
|
|
29
|
+
offset += part.byteLength;
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function pngChunkType(bytes, offset) {
|
|
35
|
+
let result = "";
|
|
36
|
+
for (let index = offset; index < offset + 4; index += 1) {
|
|
37
|
+
const value = bytes[index];
|
|
38
|
+
if (!((value >= 65 && value <= 90) || (value >= 97 && value <= 122))) {
|
|
39
|
+
fail("canonical PNG chunk type is invalid");
|
|
40
|
+
}
|
|
41
|
+
result += String.fromCharCode(value);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function crc32(bytes, start, end) {
|
|
47
|
+
let crc = 0xffffffff;
|
|
48
|
+
for (let index = start; index < end; index += 1) {
|
|
49
|
+
crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ bytes[index]) & 0xff];
|
|
50
|
+
}
|
|
51
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function stripPngMetadata(bytes) {
|
|
55
|
+
if (bytes.byteLength < 45 || PNG_SIGNATURE.some((value, index) => bytes[index] !== value)) {
|
|
56
|
+
fail("canonical PNG framing is invalid");
|
|
57
|
+
}
|
|
58
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
59
|
+
const parts = [bytes.subarray(0, PNG_SIGNATURE.length)];
|
|
60
|
+
let byteLength = PNG_SIGNATURE.length;
|
|
61
|
+
let offset = PNG_SIGNATURE.length;
|
|
62
|
+
let chunks = 0;
|
|
63
|
+
let stripped = false;
|
|
64
|
+
let sawEnd = false;
|
|
65
|
+
while (offset < bytes.byteLength) {
|
|
66
|
+
if (bytes.byteLength - offset < 12) fail("canonical PNG framing is truncated");
|
|
67
|
+
const length = view.getUint32(offset);
|
|
68
|
+
const end = offset + 12 + length;
|
|
69
|
+
if (!Number.isSafeInteger(end) || end > bytes.byteLength) fail("canonical PNG chunk is truncated");
|
|
70
|
+
const type = pngChunkType(bytes, offset + 4);
|
|
71
|
+
const expectedCrc = view.getUint32(offset + 8 + length);
|
|
72
|
+
if (crc32(bytes, offset + 4, offset + 8 + length) !== expectedCrc) {
|
|
73
|
+
fail("canonical PNG checksum is invalid");
|
|
74
|
+
}
|
|
75
|
+
chunks += 1;
|
|
76
|
+
if (chunks > 16_384) fail("canonical PNG contains too many chunks");
|
|
77
|
+
if (type === "IEND" && (length !== 0 || end !== bytes.byteLength)) {
|
|
78
|
+
fail("canonical PNG terminator is invalid");
|
|
79
|
+
}
|
|
80
|
+
if (PNG_METADATA_CHUNKS.has(type)) stripped = true;
|
|
81
|
+
else {
|
|
82
|
+
const part = bytes.subarray(offset, end);
|
|
83
|
+
parts.push(part);
|
|
84
|
+
byteLength += part.byteLength;
|
|
85
|
+
}
|
|
86
|
+
offset = end;
|
|
87
|
+
if (type === "IEND") sawEnd = true;
|
|
88
|
+
}
|
|
89
|
+
if (!sawEnd) fail("canonical PNG is incomplete");
|
|
90
|
+
return stripped ? joinedBytes(parts, byteLength) : bytes;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function stripJpegMetadata(bytes) {
|
|
94
|
+
if (bytes.byteLength < 16 || bytes[0] !== 0xff || bytes[1] !== 0xd8) {
|
|
95
|
+
fail("canonical JPEG framing is invalid");
|
|
96
|
+
}
|
|
97
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
98
|
+
const parts = [bytes.subarray(0, 2)];
|
|
99
|
+
let byteLength = 2;
|
|
100
|
+
let offset = 2;
|
|
101
|
+
let markers = 0;
|
|
102
|
+
let stripped = false;
|
|
103
|
+
let sawEnd = false;
|
|
104
|
+
while (offset < bytes.byteLength) {
|
|
105
|
+
const markerStart = offset;
|
|
106
|
+
if (bytes[offset] !== 0xff) fail("canonical JPEG marker framing is invalid");
|
|
107
|
+
while (offset < bytes.byteLength && bytes[offset] === 0xff) offset += 1;
|
|
108
|
+
if (offset >= bytes.byteLength) fail("canonical JPEG marker is truncated");
|
|
109
|
+
const marker = bytes[offset];
|
|
110
|
+
offset += 1;
|
|
111
|
+
markers += 1;
|
|
112
|
+
if (markers > 65_536) fail("canonical JPEG contains too many markers");
|
|
113
|
+
if (marker === 0xd9) {
|
|
114
|
+
if (offset !== bytes.byteLength) fail("canonical JPEG terminator is invalid");
|
|
115
|
+
const part = bytes.subarray(markerStart, offset);
|
|
116
|
+
parts.push(part);
|
|
117
|
+
byteLength += part.byteLength;
|
|
118
|
+
sawEnd = true;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
if (marker === 0xd8 || marker === 0x00 || marker === 0x01
|
|
122
|
+
|| (marker >= 0xd0 && marker <= 0xd7)) {
|
|
123
|
+
fail("canonical JPEG contains an invalid standalone marker");
|
|
124
|
+
}
|
|
125
|
+
if (bytes.byteLength - offset < 2) fail("canonical JPEG segment is truncated");
|
|
126
|
+
const length = view.getUint16(offset);
|
|
127
|
+
const end = offset + length;
|
|
128
|
+
if (length < 2 || end > bytes.byteLength) fail("canonical JPEG segment length is invalid");
|
|
129
|
+
if (JPEG_METADATA_MARKERS.has(marker)) stripped = true;
|
|
130
|
+
else {
|
|
131
|
+
const part = bytes.subarray(markerStart, end);
|
|
132
|
+
parts.push(part);
|
|
133
|
+
byteLength += part.byteLength;
|
|
134
|
+
}
|
|
135
|
+
offset = end;
|
|
136
|
+
if (marker === 0xda) {
|
|
137
|
+
const scanStart = offset;
|
|
138
|
+
while (offset < bytes.byteLength) {
|
|
139
|
+
if (bytes[offset] !== 0xff) {
|
|
140
|
+
offset += 1;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
let markerOffset = offset + 1;
|
|
144
|
+
while (markerOffset < bytes.byteLength && bytes[markerOffset] === 0xff) markerOffset += 1;
|
|
145
|
+
if (markerOffset >= bytes.byteLength) fail("canonical JPEG scan is truncated");
|
|
146
|
+
const scanMarker = bytes[markerOffset];
|
|
147
|
+
if (scanMarker === 0x00 || (scanMarker >= 0xd0 && scanMarker <= 0xd7)) {
|
|
148
|
+
offset = markerOffset + 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
const scan = bytes.subarray(scanStart, offset);
|
|
154
|
+
parts.push(scan);
|
|
155
|
+
byteLength += scan.byteLength;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!sawEnd) fail("canonical JPEG is incomplete");
|
|
159
|
+
return stripped ? joinedBytes(parts, byteLength) : bytes;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function sanitizeVisionImageBytes(bytes, mediaType) {
|
|
163
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength < 1 || bytes.byteLength > SANITIZER_BYTE_LIMIT
|
|
164
|
+
|| !["image/jpeg", "image/png"].includes(mediaType)) {
|
|
165
|
+
fail("canonical image bytes and media type are required");
|
|
166
|
+
}
|
|
167
|
+
return mediaType === "image/png" ? stripPngMetadata(bytes) : stripJpegMetadata(bytes);
|
|
168
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const WEB_RELEASE_HEADER_NAME = "x-lazying-agent-release";
|
|
2
|
+
|
|
3
|
+
const RELEASE_ID = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,95}$/u;
|
|
4
|
+
|
|
5
|
+
export function optionalWebRelease(value) {
|
|
6
|
+
if (value === undefined || value === null) return null;
|
|
7
|
+
if (typeof value !== "string" || !RELEASE_ID.test(value)) {
|
|
8
|
+
throw new TypeError("releaseId must be a portable immutable release identifier");
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function addWebReleaseHeader(headers, releaseId) {
|
|
14
|
+
if (releaseId !== null) headers.set(WEB_RELEASE_HEADER_NAME, releaseId);
|
|
15
|
+
return headers;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function inspectWebReleaseResponse(response, releaseId) {
|
|
19
|
+
if (releaseId === null) return Object.freeze({ kind: "unpinned", releaseId: null });
|
|
20
|
+
const value = response?.headers?.get?.(WEB_RELEASE_HEADER_NAME);
|
|
21
|
+
if (typeof value !== "string" || !RELEASE_ID.test(value)) {
|
|
22
|
+
return Object.freeze({ kind: "invalid", releaseId: null });
|
|
23
|
+
}
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
kind: value === releaseId ? "match" : "mismatch",
|
|
26
|
+
releaseId: value,
|
|
27
|
+
});
|
|
28
|
+
}
|