- Fix response parsing: check report field, not just message - Pass user query text to LLM instead of ignoring it - Append user question to diagnostic prompt with system context - Increase timeout to 120s for LM Studio responses - Add command_status_from_args to core/common.uc exports Closes #38
379 lines
9.3 KiB
Ucode
379 lines
9.3 KiB
Ucode
#!/usr/bin/env ucode
|
|
|
|
const PUNYCODE_BASE = 36;
|
|
const PUNYCODE_TMIN = 1;
|
|
const PUNYCODE_TMAX = 26;
|
|
const PUNYCODE_SKEW = 38;
|
|
const PUNYCODE_DAMP = 700;
|
|
const PUNYCODE_INITIAL_BIAS = 72;
|
|
const PUNYCODE_INITIAL_N = 128;
|
|
const PUNYCODE_DELIMITER = "-";
|
|
|
|
let common = require("core.common");
|
|
let as_string = common.as_string;
|
|
|
|
function ascii_lower(value) {
|
|
let upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
let lower = "abcdefghijklmnopqrstuvwxyz";
|
|
return replace(as_string(value), /[A-Z]/g, function(ch) {
|
|
return substr(lower, index(upper, ch), 1);
|
|
});
|
|
}
|
|
|
|
function byte_at(value, offset) {
|
|
return ord(substr(value, offset, 1));
|
|
}
|
|
|
|
function valid_continuation(value, offset) {
|
|
if (offset >= length(value))
|
|
return -1;
|
|
|
|
let byte = byte_at(value, offset);
|
|
return (byte & 0xc0) == 0x80 ? byte : -1;
|
|
}
|
|
|
|
function unicode_lower_codepoint(cp) {
|
|
if (cp >= 0x41 && cp <= 0x5a)
|
|
return cp + 0x20;
|
|
|
|
if (cp >= 0xc0 && cp <= 0xd6)
|
|
return cp + 0x20;
|
|
if (cp >= 0xd8 && cp <= 0xde)
|
|
return cp + 0x20;
|
|
|
|
if (cp == 0x401)
|
|
return 0x451;
|
|
if (cp >= 0x410 && cp <= 0x42f)
|
|
return cp + 0x20;
|
|
|
|
if (cp >= 0x391 && cp <= 0x3a1)
|
|
return cp + 0x20;
|
|
if (cp >= 0x3a3 && cp <= 0x3ab)
|
|
return cp + 0x20;
|
|
|
|
return cp;
|
|
}
|
|
|
|
function utf8_next_codepoint(value, offset) {
|
|
let b1 = byte_at(value, offset);
|
|
if (b1 < 0x80)
|
|
return { cp: unicode_lower_codepoint(b1), next: offset + 1 };
|
|
|
|
if (b1 >= 0xc2 && b1 <= 0xdf) {
|
|
let b2 = valid_continuation(value, offset + 1);
|
|
if (b2 < 0)
|
|
return null;
|
|
return {
|
|
cp: unicode_lower_codepoint(((b1 & 0x1f) << 6) | (b2 & 0x3f)),
|
|
next: offset + 2
|
|
};
|
|
}
|
|
|
|
if (b1 >= 0xe0 && b1 <= 0xef) {
|
|
let b2 = valid_continuation(value, offset + 1);
|
|
let b3 = valid_continuation(value, offset + 2);
|
|
if (b2 < 0 || b3 < 0)
|
|
return null;
|
|
|
|
let cp = ((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
|
|
if (cp < 0x800 || (cp >= 0xd800 && cp <= 0xdfff))
|
|
return null;
|
|
|
|
return { cp: unicode_lower_codepoint(cp), next: offset + 3 };
|
|
}
|
|
|
|
if (b1 >= 0xf0 && b1 <= 0xf4) {
|
|
let b2 = valid_continuation(value, offset + 1);
|
|
let b3 = valid_continuation(value, offset + 2);
|
|
let b4 = valid_continuation(value, offset + 3);
|
|
if (b2 < 0 || b3 < 0 || b4 < 0)
|
|
return null;
|
|
|
|
let cp = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f);
|
|
if (cp < 0x10000 || cp > 0x10ffff)
|
|
return null;
|
|
|
|
return { cp: unicode_lower_codepoint(cp), next: offset + 4 };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function utf8_codepoints(value) {
|
|
value = as_string(value);
|
|
let result = [];
|
|
|
|
for (let i = 0; i < length(value); ) {
|
|
let next = utf8_next_codepoint(value, i);
|
|
if (next == null)
|
|
return null;
|
|
|
|
push(result, next.cp);
|
|
i = next.next;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function ascii_label_char(cp) {
|
|
return (cp >= 0x30 && cp <= 0x39) ||
|
|
(cp >= 0x61 && cp <= 0x7a) ||
|
|
cp == 0x2d;
|
|
}
|
|
|
|
function valid_ascii_label(value) {
|
|
value = ascii_lower(value);
|
|
return length(value) >= 1 &&
|
|
length(value) <= 63 &&
|
|
match(value, /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/) != null;
|
|
}
|
|
|
|
function codepoints_to_ascii_label(codepoints) {
|
|
let result = "";
|
|
for (let cp in codepoints) {
|
|
if (!ascii_label_char(cp))
|
|
return null;
|
|
result += chr(cp);
|
|
}
|
|
return valid_ascii_label(result) ? result : null;
|
|
}
|
|
|
|
function punycode_digit(value) {
|
|
return chr(value < 26 ? 0x61 + value : 0x30 + value - 26);
|
|
}
|
|
|
|
function punycode_adapt(delta, numpoints, first_time) {
|
|
delta = first_time ? int(delta / PUNYCODE_DAMP) : int(delta / 2);
|
|
delta += int(delta / numpoints);
|
|
|
|
let k = 0;
|
|
while (delta > int(((PUNYCODE_BASE - PUNYCODE_TMIN) * PUNYCODE_TMAX) / 2)) {
|
|
delta = int(delta / (PUNYCODE_BASE - PUNYCODE_TMIN));
|
|
k += PUNYCODE_BASE;
|
|
}
|
|
|
|
return k + int(((PUNYCODE_BASE - PUNYCODE_TMIN + 1) * delta) / (delta + PUNYCODE_SKEW));
|
|
}
|
|
|
|
function punycode_encode(codepoints) {
|
|
let output = "";
|
|
let basic_count = 0;
|
|
|
|
for (let cp in codepoints) {
|
|
if (cp < 0x80) {
|
|
if (!ascii_label_char(cp))
|
|
return null;
|
|
output += chr(cp);
|
|
basic_count++;
|
|
}
|
|
}
|
|
|
|
let handled = basic_count;
|
|
if (basic_count > 0 && basic_count < length(codepoints))
|
|
output += PUNYCODE_DELIMITER;
|
|
|
|
let n = PUNYCODE_INITIAL_N;
|
|
let delta = 0;
|
|
let bias = PUNYCODE_INITIAL_BIAS;
|
|
|
|
while (handled < length(codepoints)) {
|
|
let m = 0x10ffff;
|
|
for (let cp in codepoints)
|
|
if (cp >= n && cp < m)
|
|
m = cp;
|
|
|
|
delta += (m - n) * (handled + 1);
|
|
n = m;
|
|
|
|
for (let cp in codepoints) {
|
|
if (cp < n) {
|
|
delta++;
|
|
continue;
|
|
}
|
|
if (cp != n)
|
|
continue;
|
|
|
|
let q = delta;
|
|
for (let k = PUNYCODE_BASE; ; k += PUNYCODE_BASE) {
|
|
let t = k <= bias
|
|
? PUNYCODE_TMIN
|
|
: (k >= bias + PUNYCODE_TMAX ? PUNYCODE_TMAX : k - bias);
|
|
if (q < t)
|
|
break;
|
|
|
|
output += punycode_digit(t + ((q - t) % (PUNYCODE_BASE - t)));
|
|
q = int((q - t) / (PUNYCODE_BASE - t));
|
|
}
|
|
|
|
output += punycode_digit(q);
|
|
bias = punycode_adapt(delta, handled + 1, handled == basic_count);
|
|
delta = 0;
|
|
handled++;
|
|
}
|
|
|
|
delta++;
|
|
n++;
|
|
}
|
|
|
|
return "xn--" + output;
|
|
}
|
|
|
|
function label_to_ascii(value) {
|
|
value = as_string(value);
|
|
if (value == "")
|
|
return null;
|
|
|
|
let codepoints = utf8_codepoints(value);
|
|
if (codepoints == null)
|
|
return null;
|
|
|
|
let ascii = codepoints_to_ascii_label(codepoints);
|
|
if (ascii != null)
|
|
return ascii;
|
|
|
|
let encoded = punycode_encode(codepoints);
|
|
return encoded != null && valid_ascii_label(encoded) ? encoded : null;
|
|
}
|
|
|
|
function domain_to_ascii(value, allow_leading_dot) {
|
|
value = trim(as_string(value));
|
|
if (value == "")
|
|
return null;
|
|
|
|
let leading_dot = false;
|
|
if (allow_leading_dot && substr(value, 0, 1) == ".") {
|
|
leading_dot = true;
|
|
value = substr(value, 1);
|
|
}
|
|
|
|
if (value == "" || substr(value, length(value) - 1, 1) == ".")
|
|
return null;
|
|
|
|
if (match(value, /^[a-z0-9_.-]+$/) != null)
|
|
return length(value) <= 253 ? (leading_dot ? "." + value : value) : null;
|
|
|
|
|
|
let labels = split(value, ".");
|
|
let result = [];
|
|
for (let label in labels) {
|
|
let normalized = label_to_ascii(label);
|
|
if (normalized == null)
|
|
return null;
|
|
push(result, normalized);
|
|
}
|
|
|
|
let domain = join(".", result);
|
|
if (length(domain) > 253)
|
|
return null;
|
|
|
|
return leading_dot ? "." + domain : domain;
|
|
}
|
|
|
|
function suffix_to_ascii(value) {
|
|
return domain_to_ascii(value, true);
|
|
}
|
|
|
|
function keyword_to_ascii(value) {
|
|
value = trim(as_string(value));
|
|
if (value == "" || match(value, /[,[:space:]]/) != null)
|
|
return null;
|
|
|
|
let has_non_ascii = false;
|
|
for (let i = 0; i < length(value); i++) {
|
|
if (byte_at(value, i) >= 0x80) {
|
|
has_non_ascii = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!has_non_ascii)
|
|
return value;
|
|
|
|
return domain_to_ascii(value, false);
|
|
}
|
|
|
|
function regex_to_ascii(value) {
|
|
value = as_string(value);
|
|
if (match(value, /[,[:space:]]/) != null)
|
|
return null;
|
|
|
|
let result = "";
|
|
let label = "";
|
|
let label_has_non_ascii = false;
|
|
let escaped = false;
|
|
let in_class = false;
|
|
|
|
function flush_label() {
|
|
if (label == "")
|
|
return true;
|
|
|
|
let normalized = label_has_non_ascii ? label_to_ascii(label) : label;
|
|
if (normalized == null)
|
|
return false;
|
|
|
|
result += normalized;
|
|
label = "";
|
|
label_has_non_ascii = false;
|
|
return true;
|
|
}
|
|
|
|
for (let i = 0; i < length(value); ) {
|
|
let next = utf8_next_codepoint(value, i);
|
|
if (next == null)
|
|
return null;
|
|
|
|
let raw = substr(value, i, next.next - i);
|
|
let cp = next.cp;
|
|
|
|
if (escaped) {
|
|
if (!flush_label())
|
|
return null;
|
|
result += raw;
|
|
escaped = false;
|
|
}
|
|
else if (cp == 0x5c) {
|
|
if (!flush_label())
|
|
return null;
|
|
result += raw;
|
|
escaped = true;
|
|
}
|
|
else if (in_class) {
|
|
result += raw;
|
|
if (cp == 0x5d)
|
|
in_class = false;
|
|
}
|
|
else if (cp == 0x5b) {
|
|
if (!flush_label())
|
|
return null;
|
|
result += raw;
|
|
in_class = true;
|
|
}
|
|
else if (ascii_label_char(cp) || cp >= 0x80) {
|
|
label += raw;
|
|
if (cp >= 0x80)
|
|
label_has_non_ascii = true;
|
|
}
|
|
else {
|
|
if (!flush_label())
|
|
return null;
|
|
result += raw;
|
|
}
|
|
|
|
i = next.next;
|
|
}
|
|
|
|
return flush_label() ? result : null;
|
|
}
|
|
|
|
function valid_suffix(value) {
|
|
return suffix_to_ascii(value) != null;
|
|
}
|
|
|
|
return {
|
|
ascii_lower,
|
|
label_to_ascii,
|
|
suffix_to_ascii,
|
|
keyword_to_ascii,
|
|
regex_to_ascii,
|
|
valid_suffix
|
|
}; |