@rebon/cli-win32-x64 1.2.0 → 1.4.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.
- package/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +203 -144
- package/THIRD_PARTY_NOTICES.txt +1232 -128
- package/bin/rebon.js +22 -22
- package/lib/windows-managed.js +390 -390
- package/package.json +9 -2
- package/payload/compose-runtime/package.json +17 -17
- package/payload/compose-runtime/payload/compose/shims/dsh-anonymous-user-id.js +10 -10
- package/payload/compose-runtime/payload/compose/shims/dsh-credentials.js +13 -13
- package/payload/compose-runtime/payload/compose/shims/dsh-launch-environment.js +21 -21
- package/payload/compose-runtime/payload/compose/shims/dsh-llm.js +313 -313
- package/payload/compose-runtime/payload/compose/shims/dsh-settings.js +43 -43
- package/payload/compose-runtime/payload/compose/shims/dsh-tools.js +20 -20
- package/payload/compose-runtime/payload/compose/shims/dsh-web.js +10 -10
- package/payload/compose-runtime/payload/compose/shims/llm-adapter.js +32 -32
- package/payload/compose-runtime/payload/compose/shims/zod-lite.js +49 -49
- package/payload/compose-runtime/payload/vendor/cordis/LICENSE +21 -0
- package/payload/compose-runtime/payload/vendor/cordis/index.js +1530 -1530
- package/payload/compose-runtime/payload/vendor/cosmokit/LICENSE +21 -21
- package/payload/compose-runtime/payload/vendor/cosmokit/index.mjs +357 -357
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent-loop +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-core +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-deepseek +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-logger-console +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-scope +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-session +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-system-prompt +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timeout +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timer +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-todo +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-web +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tools-schema +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown +21 -0
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown-plugin-gfm +21 -0
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-web-search-exa +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/agent-loop.js +1193 -1193
- package/payload/compose-runtime/payload/vendor/dsh/agent.js +714 -714
- package/payload/compose-runtime/payload/vendor/dsh/llm-core.js +269 -269
- package/payload/compose-runtime/payload/vendor/dsh/llm-deepseek.js +672 -672
- package/payload/compose-runtime/payload/vendor/dsh/logger-console.js +83 -83
- package/payload/compose-runtime/payload/vendor/dsh/scope.js +287 -287
- package/payload/compose-runtime/payload/vendor/dsh/session.js +1668 -1668
- package/payload/compose-runtime/payload/vendor/dsh/system-prompt.js +309 -309
- package/payload/compose-runtime/payload/vendor/dsh/timeout.js +100 -100
- package/payload/compose-runtime/payload/vendor/dsh/timer.js +128 -128
- package/payload/compose-runtime/payload/vendor/dsh/tool-todo.js +143 -143
- package/payload/compose-runtime/payload/vendor/dsh/tool-web.js +1527 -1527
- package/payload/compose-runtime/payload/vendor/dsh/tools-schema.js +849 -849
- package/payload/compose-runtime/payload/vendor/dsh/web-search-exa.js +122 -122
- package/payload/compose-runtime/payload/vendor/eventsource-parser/LICENSE +21 -21
- package/payload/compose-runtime/payload/vendor/eventsource-parser/index.js +177 -177
- package/payload/compose-runtime/payload/vendor/eventsource-parser/stream.js +48 -48
- package/payload/compose-runtime/payload/vendor/schemastery/LICENSE +21 -0
- package/payload/compose-runtime/payload/vendor/schemastery/index.mjs +656 -656
- package/payload/compose-runtime/payload-manifests.json +48 -48
- package/payload/compose-runtime/src/bridge.mjs +193 -195
- package/payload/compose-runtime/src/credentials-runtime.mjs +50 -50
- package/payload/compose-runtime/src/eventsource-stream.mjs +48 -48
- package/payload/compose-runtime/src/index.mjs +7 -7
- package/payload/compose-runtime/src/llm-runtime.mjs +326 -326
- package/payload/compose-runtime/src/loader.mjs +78 -78
- package/payload/compose-runtime/src/loop-assembly.mjs +237 -237
- package/payload/compose-runtime/src/payload.mjs +49 -49
- package/payload/compose-runtime/src/plugin.mjs +59 -59
- package/payload/compose-runtime/src/realm.mjs +223 -223
- package/payload/compose-runtime/src/registry.mjs +191 -191
- package/payload/compose-runtime/src/resolve.mjs +115 -115
- package/payload/compose-runtime/src/systemprompt-runtime.mjs +67 -67
- package/payload/compose-runtime/src/tools-runtime.mjs +239 -239
- package/payload/compose-runtime/src/web-runtime.mjs +209 -209
- package/payload/plugin-host/package.json +11 -11
- package/payload/plugin-host/src/bridge.mjs +64 -64
- package/payload/plugin-host/src/cli.mjs +147 -147
- package/payload/plugin-host/src/framing.mjs +113 -113
- package/payload/plugin-host/src/host.mjs +691 -691
- package/payload/plugin-host/src/json.mjs +208 -208
- package/payload/plugin-host/src/lifecycle.mjs +114 -114
- package/payload/plugin-host/src/loader.mjs +183 -183
- package/payload/plugin-host/src/methods.mjs +377 -377
- package/payload/plugin-host/src/ownership.mjs +57 -57
- package/payload/plugin-host/src/protocol.mjs +129 -129
- package/payload/plugin-host/src/sdk.mjs +7 -7
- package/payload/rebon-boa-helper.exe +0 -0
- package/payload/rebon-browser-mcp.exe +0 -0
- package/payload/rebon-computer-use.exe +0 -0
- package/payload/rebon-lsp-mcp.exe +0 -0
- package/payload/rebon.exe +2 -2
- package/payload/sandbox-win.exe +0 -0
- package/scripts/postinstall.js +112 -112
- package/payload/compose-runtime/payload/compose/credentials-runtime.js +0 -38
- package/payload/compose-runtime/payload/compose/llm-runtime.js +0 -330
- package/payload/compose-runtime/payload/compose/loop-assembly.js +0 -149
- package/payload/compose-runtime/payload/compose/serve-dispatch.js +0 -82
- package/payload/compose-runtime/payload/compose/systemprompt-runtime.js +0 -74
- package/payload/compose-runtime/payload/compose/tools-runtime.js +0 -228
- package/payload/compose-runtime/payload/compose/web-runtime.js +0 -204
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
import { ProtocolError, RawJsonNumber, validateEnvelope } from './protocol.mjs';
|
|
2
|
-
|
|
3
|
-
class RawNumber {
|
|
4
|
-
constructor(raw) { this.raw = raw; Object.freeze(this); }
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
class JsonParser {
|
|
8
|
-
constructor(text) { this.text = text; this.index = 0; }
|
|
9
|
-
parse() {
|
|
10
|
-
this.#space();
|
|
11
|
-
const value = this.#value('envelope');
|
|
12
|
-
this.#space();
|
|
13
|
-
if (this.index !== this.text.length) this.#error('trailing_json', 'trailing data after JSON value');
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
#value(role = 'value') {
|
|
17
|
-
const c = this.text[this.index];
|
|
18
|
-
if (c === '{') return this.#object(role);
|
|
19
|
-
if (c === '[') return this.#array();
|
|
20
|
-
if (c === '"') return this.#string();
|
|
21
|
-
if (c === 't') return this.#literal('true', true);
|
|
22
|
-
if (c === 'f') return this.#literal('false', false);
|
|
23
|
-
if (c === 'n') return this.#literal('null', null);
|
|
24
|
-
if (c === '-' || (c >= '0' && c <= '9')) return this.#number();
|
|
25
|
-
this.#error('malformed_json', 'expected a JSON value');
|
|
26
|
-
}
|
|
27
|
-
#object(role) {
|
|
28
|
-
this.index++;
|
|
29
|
-
const object = Object.create(null);
|
|
30
|
-
const keys = new Set();
|
|
31
|
-
this.#space();
|
|
32
|
-
if (this.text[this.index] === '}') { this.index++; return object; }
|
|
33
|
-
for (;;) {
|
|
34
|
-
if (this.text[this.index] !== '"') this.#error('malformed_json', 'object key must be a string');
|
|
35
|
-
const key = this.#string();
|
|
36
|
-
this.#space();
|
|
37
|
-
if (this.text[this.index++] !== ':') this.#error('malformed_json', 'expected colon after object key');
|
|
38
|
-
this.#space();
|
|
39
|
-
const childRole = role === 'envelope' && key === 'message' ? 'message' : 'value';
|
|
40
|
-
const value = this.#value(childRole);
|
|
41
|
-
if ((role === 'envelope' || role === 'message') && keys.has(key)) {
|
|
42
|
-
this.#error('duplicate_field', `duplicate field in ${role}`);
|
|
43
|
-
}
|
|
44
|
-
keys.add(key);
|
|
45
|
-
object[key] = value; // serde_json::Value uses the last value for opaque object duplicates.
|
|
46
|
-
this.#space();
|
|
47
|
-
const delimiter = this.text[this.index++];
|
|
48
|
-
if (delimiter === '}') return object;
|
|
49
|
-
if (delimiter !== ',') this.#error('malformed_json', 'expected comma or object end');
|
|
50
|
-
this.#space();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
#array() {
|
|
54
|
-
this.index++;
|
|
55
|
-
const array = [];
|
|
56
|
-
this.#space();
|
|
57
|
-
if (this.text[this.index] === ']') { this.index++; return array; }
|
|
58
|
-
for (;;) {
|
|
59
|
-
array.push(this.#value());
|
|
60
|
-
this.#space();
|
|
61
|
-
const delimiter = this.text[this.index++];
|
|
62
|
-
if (delimiter === ']') return array;
|
|
63
|
-
if (delimiter !== ',') this.#error('malformed_json', 'expected comma or array end');
|
|
64
|
-
this.#space();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
#string() {
|
|
68
|
-
this.index++;
|
|
69
|
-
let result = '';
|
|
70
|
-
for (;;) {
|
|
71
|
-
if (this.index >= this.text.length) this.#error('malformed_json', 'unterminated JSON string');
|
|
72
|
-
const c = this.text[this.index++];
|
|
73
|
-
if (c === '"') return result;
|
|
74
|
-
if (c === '\\') {
|
|
75
|
-
if (this.index >= this.text.length) this.#error('malformed_json', 'unterminated JSON escape');
|
|
76
|
-
const escape = this.text[this.index++];
|
|
77
|
-
const simple = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' }[escape];
|
|
78
|
-
if (simple !== undefined) { result += simple; continue; }
|
|
79
|
-
if (escape !== 'u') this.#error('malformed_json', 'invalid JSON escape');
|
|
80
|
-
const first = this.#hexUnit();
|
|
81
|
-
if (first >= 0xd800 && first <= 0xdbff) {
|
|
82
|
-
if (this.text.slice(this.index, this.index + 2) !== '\\u') this.#error('invalid_surrogate', 'lone high surrogate in JSON string');
|
|
83
|
-
this.index += 2;
|
|
84
|
-
const second = this.#hexUnit();
|
|
85
|
-
if (second < 0xdc00 || second > 0xdfff) this.#error('invalid_surrogate', 'invalid surrogate pair in JSON string');
|
|
86
|
-
result += String.fromCodePoint(0x10000 + ((first - 0xd800) << 10) + second - 0xdc00);
|
|
87
|
-
} else if (first >= 0xdc00 && first <= 0xdfff) {
|
|
88
|
-
this.#error('invalid_surrogate', 'lone low surrogate in JSON string');
|
|
89
|
-
} else result += String.fromCharCode(first);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const unit = c.charCodeAt(0);
|
|
93
|
-
if (unit < 0x20) this.#error('malformed_json', 'unescaped control character in JSON string');
|
|
94
|
-
// The text is UTF-16, so a raw character outside the BMP (an emoji, as
|
|
95
|
-
// serde_json writes it unescaped) is two units here: a high surrogate
|
|
96
|
-
// followed by a low one. Only a half without its partner is invalid.
|
|
97
|
-
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
98
|
-
const next = this.text.charCodeAt(this.index);
|
|
99
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
100
|
-
result += c + this.text[this.index++];
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
this.#error('invalid_surrogate', 'lone surrogate in JSON string');
|
|
104
|
-
}
|
|
105
|
-
if (unit >= 0xdc00 && unit <= 0xdfff) this.#error('invalid_surrogate', 'lone surrogate in JSON string');
|
|
106
|
-
result += c;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
#hexUnit() {
|
|
110
|
-
const token = this.text.slice(this.index, this.index + 4);
|
|
111
|
-
if (!/^[0-9a-fA-F]{4}$/.test(token)) this.#error('malformed_json', 'invalid unicode escape');
|
|
112
|
-
this.index += 4;
|
|
113
|
-
return Number.parseInt(token, 16);
|
|
114
|
-
}
|
|
115
|
-
#number() {
|
|
116
|
-
const start = this.index;
|
|
117
|
-
if (this.text[this.index] === '-') this.index++;
|
|
118
|
-
if (this.text[this.index] === '0') this.index++;
|
|
119
|
-
else {
|
|
120
|
-
if (this.text[this.index] < '1' || this.text[this.index] > '9') this.#error('malformed_json', 'invalid JSON number');
|
|
121
|
-
while (this.text[this.index] >= '0' && this.text[this.index] <= '9') this.index++;
|
|
122
|
-
}
|
|
123
|
-
if (this.text[this.index] === '.') {
|
|
124
|
-
this.index++;
|
|
125
|
-
const fraction = this.index;
|
|
126
|
-
while (this.text[this.index] >= '0' && this.text[this.index] <= '9') this.index++;
|
|
127
|
-
if (fraction === this.index) this.#error('malformed_json', 'fraction requires digits');
|
|
128
|
-
}
|
|
129
|
-
if (this.text[this.index] === 'e' || this.text[this.index] === 'E') {
|
|
130
|
-
this.index++;
|
|
131
|
-
if (this.text[this.index] === '+' || this.text[this.index] === '-') this.index++;
|
|
132
|
-
const exponent = this.index;
|
|
133
|
-
while (this.text[this.index] >= '0' && this.text[this.index] <= '9') this.index++;
|
|
134
|
-
if (exponent === this.index) this.#error('malformed_json', 'exponent requires digits');
|
|
135
|
-
}
|
|
136
|
-
return new RawNumber(this.text.slice(start, this.index));
|
|
137
|
-
}
|
|
138
|
-
#literal(token, value) {
|
|
139
|
-
if (this.text.slice(this.index, this.index + token.length) !== token) this.#error('malformed_json', 'invalid JSON literal');
|
|
140
|
-
this.index += token.length;
|
|
141
|
-
return value;
|
|
142
|
-
}
|
|
143
|
-
#space() { while (' \t\r\n'.includes(this.text[this.index] ?? '\0')) this.index++; }
|
|
144
|
-
#error(code, message) { throw new ProtocolError(code, message); }
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function exponentValue(raw) {
|
|
148
|
-
const marker = Math.max(raw.indexOf('e'), raw.indexOf('E'));
|
|
149
|
-
if (marker < 0) return 0;
|
|
150
|
-
let value = raw.slice(marker + 1);
|
|
151
|
-
let sign = 1;
|
|
152
|
-
if (value[0] === '+' || value[0] === '-') { if (value[0] === '-') sign = -1; value = value.slice(1); }
|
|
153
|
-
value = value.replace(/^0+/, '') || '0';
|
|
154
|
-
if (value.length > 9) return sign * Infinity;
|
|
155
|
-
return sign * Number(value);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function parseSafeIntegerToken(raw, field) {
|
|
159
|
-
const negative = raw[0] === '-';
|
|
160
|
-
const unsigned = negative ? raw.slice(1) : raw;
|
|
161
|
-
const marker = Math.max(unsigned.indexOf('e'), unsigned.indexOf('E'));
|
|
162
|
-
const mantissa = marker < 0 ? unsigned : unsigned.slice(0, marker);
|
|
163
|
-
const dot = mantissa.indexOf('.');
|
|
164
|
-
const fractionLength = dot < 0 ? 0 : mantissa.length - dot - 1;
|
|
165
|
-
let digits = (dot < 0 ? mantissa : mantissa.slice(0, dot) + mantissa.slice(dot + 1)).replace(/^0+/, '');
|
|
166
|
-
if (!digits) return 0;
|
|
167
|
-
if (negative) throw new ProtocolError('unsafe_integer', `${field} must be a safe non-negative integer`);
|
|
168
|
-
const scale = exponentValue(unsigned) - fractionLength;
|
|
169
|
-
if (!Number.isFinite(scale)) throw new ProtocolError('unsafe_integer', `${field} must be a safe non-negative integer`);
|
|
170
|
-
if (scale < 0) {
|
|
171
|
-
const remove = -scale;
|
|
172
|
-
if (remove > digits.length || !digits.endsWith('0'.repeat(remove))) throw new ProtocolError('unsafe_integer', `${field} must be an exact integer`);
|
|
173
|
-
digits = digits.slice(0, digits.length - remove).replace(/^0+/, '') || '0';
|
|
174
|
-
} else {
|
|
175
|
-
if (digits.length + scale > 16) throw new ProtocolError('unsafe_integer', `${field} must be a safe non-negative integer`);
|
|
176
|
-
digits += '0'.repeat(scale);
|
|
177
|
-
}
|
|
178
|
-
if (digits.length > 16 || (digits.length === 16 && digits > '9007199254740991')) {
|
|
179
|
-
throw new ProtocolError('unsafe_integer', `${field} must be a safe non-negative integer`);
|
|
180
|
-
}
|
|
181
|
-
return Number(digits);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function materialize(value) {
|
|
185
|
-
if (value instanceof RawNumber) {
|
|
186
|
-
const number = Number(value.raw);
|
|
187
|
-
if (/^-?(?:0|[1-9][0-9]*)$/.test(value.raw) && Number.isSafeInteger(number)) return Object.is(number, -0) ? 0 : number;
|
|
188
|
-
return new RawJsonNumber(value.raw);
|
|
189
|
-
}
|
|
190
|
-
if (Array.isArray(value)) return value.map(materialize);
|
|
191
|
-
if (value && typeof value === 'object') for (const key of Object.keys(value)) value[key] = materialize(value[key]);
|
|
192
|
-
return value;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function parseEnvelopeJson(text) {
|
|
196
|
-
const value = new JsonParser(text).parse();
|
|
197
|
-
if (!value || value instanceof RawNumber || Array.isArray(value) || typeof value !== 'object') throw new ProtocolError('wrong_shape', 'envelope must be an object');
|
|
198
|
-
if (!(value.protocol_version instanceof RawNumber) || value.protocol_version.raw !== '1') {
|
|
199
|
-
throw new ProtocolError('bad_version', 'protocol_version must be integer token 1');
|
|
200
|
-
}
|
|
201
|
-
value.protocol_version = 1;
|
|
202
|
-
for (const field of ['host_epoch', 'scope_generation']) {
|
|
203
|
-
if (!(value[field] instanceof RawNumber)) throw new ProtocolError('unsafe_integer', `${field} must be a JSON number`);
|
|
204
|
-
value[field] = parseSafeIntegerToken(value[field].raw, field);
|
|
205
|
-
}
|
|
206
|
-
value.message = materialize(value.message);
|
|
207
|
-
return validateEnvelope(value);
|
|
208
|
-
}
|
|
1
|
+
import { ProtocolError, RawJsonNumber, validateEnvelope } from './protocol.mjs';
|
|
2
|
+
|
|
3
|
+
class RawNumber {
|
|
4
|
+
constructor(raw) { this.raw = raw; Object.freeze(this); }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class JsonParser {
|
|
8
|
+
constructor(text) { this.text = text; this.index = 0; }
|
|
9
|
+
parse() {
|
|
10
|
+
this.#space();
|
|
11
|
+
const value = this.#value('envelope');
|
|
12
|
+
this.#space();
|
|
13
|
+
if (this.index !== this.text.length) this.#error('trailing_json', 'trailing data after JSON value');
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
#value(role = 'value') {
|
|
17
|
+
const c = this.text[this.index];
|
|
18
|
+
if (c === '{') return this.#object(role);
|
|
19
|
+
if (c === '[') return this.#array();
|
|
20
|
+
if (c === '"') return this.#string();
|
|
21
|
+
if (c === 't') return this.#literal('true', true);
|
|
22
|
+
if (c === 'f') return this.#literal('false', false);
|
|
23
|
+
if (c === 'n') return this.#literal('null', null);
|
|
24
|
+
if (c === '-' || (c >= '0' && c <= '9')) return this.#number();
|
|
25
|
+
this.#error('malformed_json', 'expected a JSON value');
|
|
26
|
+
}
|
|
27
|
+
#object(role) {
|
|
28
|
+
this.index++;
|
|
29
|
+
const object = Object.create(null);
|
|
30
|
+
const keys = new Set();
|
|
31
|
+
this.#space();
|
|
32
|
+
if (this.text[this.index] === '}') { this.index++; return object; }
|
|
33
|
+
for (;;) {
|
|
34
|
+
if (this.text[this.index] !== '"') this.#error('malformed_json', 'object key must be a string');
|
|
35
|
+
const key = this.#string();
|
|
36
|
+
this.#space();
|
|
37
|
+
if (this.text[this.index++] !== ':') this.#error('malformed_json', 'expected colon after object key');
|
|
38
|
+
this.#space();
|
|
39
|
+
const childRole = role === 'envelope' && key === 'message' ? 'message' : 'value';
|
|
40
|
+
const value = this.#value(childRole);
|
|
41
|
+
if ((role === 'envelope' || role === 'message') && keys.has(key)) {
|
|
42
|
+
this.#error('duplicate_field', `duplicate field in ${role}`);
|
|
43
|
+
}
|
|
44
|
+
keys.add(key);
|
|
45
|
+
object[key] = value; // serde_json::Value uses the last value for opaque object duplicates.
|
|
46
|
+
this.#space();
|
|
47
|
+
const delimiter = this.text[this.index++];
|
|
48
|
+
if (delimiter === '}') return object;
|
|
49
|
+
if (delimiter !== ',') this.#error('malformed_json', 'expected comma or object end');
|
|
50
|
+
this.#space();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
#array() {
|
|
54
|
+
this.index++;
|
|
55
|
+
const array = [];
|
|
56
|
+
this.#space();
|
|
57
|
+
if (this.text[this.index] === ']') { this.index++; return array; }
|
|
58
|
+
for (;;) {
|
|
59
|
+
array.push(this.#value());
|
|
60
|
+
this.#space();
|
|
61
|
+
const delimiter = this.text[this.index++];
|
|
62
|
+
if (delimiter === ']') return array;
|
|
63
|
+
if (delimiter !== ',') this.#error('malformed_json', 'expected comma or array end');
|
|
64
|
+
this.#space();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
#string() {
|
|
68
|
+
this.index++;
|
|
69
|
+
let result = '';
|
|
70
|
+
for (;;) {
|
|
71
|
+
if (this.index >= this.text.length) this.#error('malformed_json', 'unterminated JSON string');
|
|
72
|
+
const c = this.text[this.index++];
|
|
73
|
+
if (c === '"') return result;
|
|
74
|
+
if (c === '\\') {
|
|
75
|
+
if (this.index >= this.text.length) this.#error('malformed_json', 'unterminated JSON escape');
|
|
76
|
+
const escape = this.text[this.index++];
|
|
77
|
+
const simple = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' }[escape];
|
|
78
|
+
if (simple !== undefined) { result += simple; continue; }
|
|
79
|
+
if (escape !== 'u') this.#error('malformed_json', 'invalid JSON escape');
|
|
80
|
+
const first = this.#hexUnit();
|
|
81
|
+
if (first >= 0xd800 && first <= 0xdbff) {
|
|
82
|
+
if (this.text.slice(this.index, this.index + 2) !== '\\u') this.#error('invalid_surrogate', 'lone high surrogate in JSON string');
|
|
83
|
+
this.index += 2;
|
|
84
|
+
const second = this.#hexUnit();
|
|
85
|
+
if (second < 0xdc00 || second > 0xdfff) this.#error('invalid_surrogate', 'invalid surrogate pair in JSON string');
|
|
86
|
+
result += String.fromCodePoint(0x10000 + ((first - 0xd800) << 10) + second - 0xdc00);
|
|
87
|
+
} else if (first >= 0xdc00 && first <= 0xdfff) {
|
|
88
|
+
this.#error('invalid_surrogate', 'lone low surrogate in JSON string');
|
|
89
|
+
} else result += String.fromCharCode(first);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const unit = c.charCodeAt(0);
|
|
93
|
+
if (unit < 0x20) this.#error('malformed_json', 'unescaped control character in JSON string');
|
|
94
|
+
// The text is UTF-16, so a raw character outside the BMP (an emoji, as
|
|
95
|
+
// serde_json writes it unescaped) is two units here: a high surrogate
|
|
96
|
+
// followed by a low one. Only a half without its partner is invalid.
|
|
97
|
+
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
98
|
+
const next = this.text.charCodeAt(this.index);
|
|
99
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
100
|
+
result += c + this.text[this.index++];
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
this.#error('invalid_surrogate', 'lone surrogate in JSON string');
|
|
104
|
+
}
|
|
105
|
+
if (unit >= 0xdc00 && unit <= 0xdfff) this.#error('invalid_surrogate', 'lone surrogate in JSON string');
|
|
106
|
+
result += c;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
#hexUnit() {
|
|
110
|
+
const token = this.text.slice(this.index, this.index + 4);
|
|
111
|
+
if (!/^[0-9a-fA-F]{4}$/.test(token)) this.#error('malformed_json', 'invalid unicode escape');
|
|
112
|
+
this.index += 4;
|
|
113
|
+
return Number.parseInt(token, 16);
|
|
114
|
+
}
|
|
115
|
+
#number() {
|
|
116
|
+
const start = this.index;
|
|
117
|
+
if (this.text[this.index] === '-') this.index++;
|
|
118
|
+
if (this.text[this.index] === '0') this.index++;
|
|
119
|
+
else {
|
|
120
|
+
if (this.text[this.index] < '1' || this.text[this.index] > '9') this.#error('malformed_json', 'invalid JSON number');
|
|
121
|
+
while (this.text[this.index] >= '0' && this.text[this.index] <= '9') this.index++;
|
|
122
|
+
}
|
|
123
|
+
if (this.text[this.index] === '.') {
|
|
124
|
+
this.index++;
|
|
125
|
+
const fraction = this.index;
|
|
126
|
+
while (this.text[this.index] >= '0' && this.text[this.index] <= '9') this.index++;
|
|
127
|
+
if (fraction === this.index) this.#error('malformed_json', 'fraction requires digits');
|
|
128
|
+
}
|
|
129
|
+
if (this.text[this.index] === 'e' || this.text[this.index] === 'E') {
|
|
130
|
+
this.index++;
|
|
131
|
+
if (this.text[this.index] === '+' || this.text[this.index] === '-') this.index++;
|
|
132
|
+
const exponent = this.index;
|
|
133
|
+
while (this.text[this.index] >= '0' && this.text[this.index] <= '9') this.index++;
|
|
134
|
+
if (exponent === this.index) this.#error('malformed_json', 'exponent requires digits');
|
|
135
|
+
}
|
|
136
|
+
return new RawNumber(this.text.slice(start, this.index));
|
|
137
|
+
}
|
|
138
|
+
#literal(token, value) {
|
|
139
|
+
if (this.text.slice(this.index, this.index + token.length) !== token) this.#error('malformed_json', 'invalid JSON literal');
|
|
140
|
+
this.index += token.length;
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
#space() { while (' \t\r\n'.includes(this.text[this.index] ?? '\0')) this.index++; }
|
|
144
|
+
#error(code, message) { throw new ProtocolError(code, message); }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function exponentValue(raw) {
|
|
148
|
+
const marker = Math.max(raw.indexOf('e'), raw.indexOf('E'));
|
|
149
|
+
if (marker < 0) return 0;
|
|
150
|
+
let value = raw.slice(marker + 1);
|
|
151
|
+
let sign = 1;
|
|
152
|
+
if (value[0] === '+' || value[0] === '-') { if (value[0] === '-') sign = -1; value = value.slice(1); }
|
|
153
|
+
value = value.replace(/^0+/, '') || '0';
|
|
154
|
+
if (value.length > 9) return sign * Infinity;
|
|
155
|
+
return sign * Number(value);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function parseSafeIntegerToken(raw, field) {
|
|
159
|
+
const negative = raw[0] === '-';
|
|
160
|
+
const unsigned = negative ? raw.slice(1) : raw;
|
|
161
|
+
const marker = Math.max(unsigned.indexOf('e'), unsigned.indexOf('E'));
|
|
162
|
+
const mantissa = marker < 0 ? unsigned : unsigned.slice(0, marker);
|
|
163
|
+
const dot = mantissa.indexOf('.');
|
|
164
|
+
const fractionLength = dot < 0 ? 0 : mantissa.length - dot - 1;
|
|
165
|
+
let digits = (dot < 0 ? mantissa : mantissa.slice(0, dot) + mantissa.slice(dot + 1)).replace(/^0+/, '');
|
|
166
|
+
if (!digits) return 0;
|
|
167
|
+
if (negative) throw new ProtocolError('unsafe_integer', `${field} must be a safe non-negative integer`);
|
|
168
|
+
const scale = exponentValue(unsigned) - fractionLength;
|
|
169
|
+
if (!Number.isFinite(scale)) throw new ProtocolError('unsafe_integer', `${field} must be a safe non-negative integer`);
|
|
170
|
+
if (scale < 0) {
|
|
171
|
+
const remove = -scale;
|
|
172
|
+
if (remove > digits.length || !digits.endsWith('0'.repeat(remove))) throw new ProtocolError('unsafe_integer', `${field} must be an exact integer`);
|
|
173
|
+
digits = digits.slice(0, digits.length - remove).replace(/^0+/, '') || '0';
|
|
174
|
+
} else {
|
|
175
|
+
if (digits.length + scale > 16) throw new ProtocolError('unsafe_integer', `${field} must be a safe non-negative integer`);
|
|
176
|
+
digits += '0'.repeat(scale);
|
|
177
|
+
}
|
|
178
|
+
if (digits.length > 16 || (digits.length === 16 && digits > '9007199254740991')) {
|
|
179
|
+
throw new ProtocolError('unsafe_integer', `${field} must be a safe non-negative integer`);
|
|
180
|
+
}
|
|
181
|
+
return Number(digits);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function materialize(value) {
|
|
185
|
+
if (value instanceof RawNumber) {
|
|
186
|
+
const number = Number(value.raw);
|
|
187
|
+
if (/^-?(?:0|[1-9][0-9]*)$/.test(value.raw) && Number.isSafeInteger(number)) return Object.is(number, -0) ? 0 : number;
|
|
188
|
+
return new RawJsonNumber(value.raw);
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(value)) return value.map(materialize);
|
|
191
|
+
if (value && typeof value === 'object') for (const key of Object.keys(value)) value[key] = materialize(value[key]);
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function parseEnvelopeJson(text) {
|
|
196
|
+
const value = new JsonParser(text).parse();
|
|
197
|
+
if (!value || value instanceof RawNumber || Array.isArray(value) || typeof value !== 'object') throw new ProtocolError('wrong_shape', 'envelope must be an object');
|
|
198
|
+
if (!(value.protocol_version instanceof RawNumber) || value.protocol_version.raw !== '1') {
|
|
199
|
+
throw new ProtocolError('bad_version', 'protocol_version must be integer token 1');
|
|
200
|
+
}
|
|
201
|
+
value.protocol_version = 1;
|
|
202
|
+
for (const field of ['host_epoch', 'scope_generation']) {
|
|
203
|
+
if (!(value[field] instanceof RawNumber)) throw new ProtocolError('unsafe_integer', `${field} must be a JSON number`);
|
|
204
|
+
value[field] = parseSafeIntegerToken(value[field].raw, field);
|
|
205
|
+
}
|
|
206
|
+
value.message = materialize(value.message);
|
|
207
|
+
return validateEnvelope(value);
|
|
208
|
+
}
|