Files
playbook/antigravity-awesome-skills/tools/lib/aas-v1/mcp/strict-json.js
T
2026-07-20 00:03:02 +00:00

158 lines
4.2 KiB
JavaScript

"use strict";
const MAX_LINE_BYTES = 256 * 1024;
const MAX_JSON_DEPTH = 16;
class StrictJsonError extends Error {
constructor(code) {
super(code);
this.name = "StrictJsonError";
this.code = code;
}
}
function strictJsonError(code) {
throw new StrictJsonError(code);
}
function scanJson(text, maximumDepth = MAX_JSON_DEPTH) {
let cursor = 0;
function skipWhitespace() {
while (cursor < text.length && /[\u0009\u000a\u000d\u0020]/.test(text[cursor])) cursor += 1;
}
function scanString() {
if (text[cursor] !== "\"") strictJsonError("AAS_MCP_JSON_STRING_EXPECTED");
const start = cursor;
cursor += 1;
while (cursor < text.length) {
const character = text[cursor];
if (character === "\"") {
cursor += 1;
const raw = text.slice(start, cursor);
try {
return JSON.parse(raw);
} catch {
strictJsonError("AAS_MCP_JSON_STRING_INVALID");
}
}
if (character === "\\") {
cursor += 2;
} else {
cursor += 1;
}
}
strictJsonError("AAS_MCP_JSON_STRING_UNTERMINATED");
}
function scanPrimitive() {
const start = cursor;
while (cursor < text.length && !/[\u0009\u000a\u000d\u0020,}\]]/.test(text[cursor])) cursor += 1;
if (start === cursor) strictJsonError("AAS_MCP_JSON_VALUE_EXPECTED");
try {
JSON.parse(text.slice(start, cursor));
} catch {
strictJsonError("AAS_MCP_JSON_PRIMITIVE_INVALID");
}
}
function scanValue(depth) {
skipWhitespace();
if (cursor >= text.length) strictJsonError("AAS_MCP_JSON_VALUE_EXPECTED");
const character = text[cursor];
if (character === "{") return scanObject(depth + 1);
if (character === "[") return scanArray(depth + 1);
if (character === "\"") return scanString();
return scanPrimitive();
}
function assertDepth(depth) {
if (depth > maximumDepth) strictJsonError("AAS_MCP_JSON_DEPTH_EXCEEDED");
}
function scanObject(depth) {
assertDepth(depth);
const keys = new Set();
cursor += 1;
skipWhitespace();
if (text[cursor] === "}") {
cursor += 1;
return;
}
while (cursor < text.length) {
skipWhitespace();
const key = scanString();
if (keys.has(key)) strictJsonError("AAS_MCP_JSON_DUPLICATE_KEY");
keys.add(key);
skipWhitespace();
if (text[cursor] !== ":") strictJsonError("AAS_MCP_JSON_COLON_EXPECTED");
cursor += 1;
scanValue(depth);
skipWhitespace();
if (text[cursor] === "}") {
cursor += 1;
return;
}
if (text[cursor] !== ",") strictJsonError("AAS_MCP_JSON_SEPARATOR_EXPECTED");
cursor += 1;
}
strictJsonError("AAS_MCP_JSON_OBJECT_UNTERMINATED");
}
function scanArray(depth) {
assertDepth(depth);
cursor += 1;
skipWhitespace();
if (text[cursor] === "]") {
cursor += 1;
return;
}
while (cursor < text.length) {
scanValue(depth);
skipWhitespace();
if (text[cursor] === "]") {
cursor += 1;
return;
}
if (text[cursor] !== ",") strictJsonError("AAS_MCP_JSON_SEPARATOR_EXPECTED");
cursor += 1;
}
strictJsonError("AAS_MCP_JSON_ARRAY_UNTERMINATED");
}
scanValue(0);
skipWhitespace();
if (cursor !== text.length) strictJsonError("AAS_MCP_JSON_TRAILING_DATA");
}
function parseStrictJsonLine(bytes, options = {}) {
const buffer = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
const maximumBytes = options.maximumBytes ?? MAX_LINE_BYTES;
if (buffer.length > maximumBytes) strictJsonError("AAS_MCP_LINE_TOO_LARGE");
let text;
try {
text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
} catch {
strictJsonError("AAS_MCP_UTF8_INVALID");
}
scanJson(text, options.maximumDepth ?? MAX_JSON_DEPTH);
let value;
try {
value = JSON.parse(text);
} catch {
strictJsonError("AAS_MCP_JSON_INVALID");
}
if (Array.isArray(value)) strictJsonError("AAS_MCP_JSONRPC_BATCH_FORBIDDEN");
if (!value || typeof value !== "object") strictJsonError("AAS_MCP_JSONRPC_OBJECT_REQUIRED");
return value;
}
module.exports = {
MAX_JSON_DEPTH,
MAX_LINE_BYTES,
StrictJsonError,
parseStrictJsonLine,
scanJson,
};