Files
ucxl-vscode/extension.js
anthonyrawlins 8d40cd98ac Initial commit - UCXL VS Code extension
- Added UCXL VS Code extension with syntax highlighting
- Implemented language configuration and grammar definitions
- Created extension package with examples and documentation
- Added syntax highlighting for UCXL code structures

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 09:39:31 +10:00

262 lines
9.2 KiB
JavaScript

const vscode = require("vscode");
/* const UCXL_REGEX = /^ucxl:\/\/([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)@([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)((\/(\^\^|~~))?\/[^\\s]*)?$/; */
// Updated regex to match the new UCXL address format with optional temporal and context path
// The regex captures:
// - Agent and Role: alphanumeric with underscores and hyphens
// - Project and Task: alphanumeric with underscores and hyphens
// - Temporal: optional, can be `#`, `~*`, `^*`, `~~`, `^^`, `~<number>`, or `^<number>`
// - Context Path: optional, can be any string after the temporal part
// The regex ensures that the address starts with `ucxl://` and is followed by the correct structure of components.
// The regex is designed to be flexible while ensuring that the components are valid according to the UCXL specification.
// The regex also allows for the context path to be any valid string, ensuring it captures the full address correctly.
// The regex is designed to be used in a VSCode extension for validating and providing hover information
// about UCXL addresses in text documents.
//const UCXL_REGEX = /^ucxl:\/\/([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)@([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)\/(#|~\*|\^\*|~~|\^\^|~\d+|\^\d+)\/(.+)$/;
// Agent ID regex for UCXL addresses
const UCXL_REGEX = /^ucxl:\/\/([A-Z0-9]{3,5}|any|none):([a-zA-Z0-9_-]+)@([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)\/(#|~\*|\^\*|~~|\^\^|~\d+|\^\d+)\/(.+)$/;
function activate(context) {
const diagnosticCollection = vscode.languages.createDiagnosticCollection("ucxl");
context.subscriptions.push(diagnosticCollection);
function updateDiagnostics(doc) {
if (!doc) return;
const diagnostics = [];
const text = doc.getText();
const lines = text.split(/\r?\n/);
lines.forEach((line, idx) => {
const matches = line.matchAll(/ucxl:\/\/[^\s]+/g);
for (const match of matches) {
const addr = match[0];
if (!UCXL_REGEX.test(addr)) {
diagnostics.push(
new vscode.Diagnostic(
new vscode.Range(idx, match.index, idx, match.index + addr.length),
"Invalid UCXL address syntax",
vscode.DiagnosticSeverity.Error
)
);
}
}
});
diagnosticCollection.set(doc.uri, diagnostics);
}
vscode.workspace.onDidOpenTextDocument(updateDiagnostics);
vscode.workspace.onDidChangeTextDocument(e => updateDiagnostics(e.document));
vscode.workspace.onDidCloseTextDocument(doc => diagnosticCollection.delete(doc.uri));
// Hover Provider
const hoverProvider = vscode.languages.registerHoverProvider(
{ scheme: "file", language: "*" },
{
provideHover(document, position) {
const range = document.getWordRangeAtPosition(position, /ucxl:\/\/[^\s]+/);
if (!range) return;
const address = document.getText(range);
const match = address.match(UCXL_REGEX);
if (!match) return new vscode.Hover(`$(error) **Invalid UCXL address**`);
const [, agent, role, project, task, , temporal, , context_path] = match;
const md = new vscode.MarkdownString();
md.isTrusted = true;
md.appendMarkdown(`### UCXL Address Components\n`);
md.appendMarkdown(`- **Agent**: \`${agent}\`\n`);
md.appendMarkdown(`- **Role**: \`${role}\`\n`);
md.appendMarkdown(`- **Project**: \`${project}\`\n`);
md.appendMarkdown(`- **Task**: \`${task}\`\n`);
if (temporal) md.appendMarkdown(`- **Temporal**: \`${temporal}\`\n`);
if (context_path) md.appendMarkdown(`- **Context Path**: \`${context_path}\`\n`);
return new vscode.Hover(md, range);
}
}
);
context.subscriptions.push(hoverProvider);
}
function lintUcxlLine(lineText, lineNum, diagnostics) {
const match = lineText.match(UCXL_REGEX);
if (!match) return;
const fullAddress = match[0];
const agentToken = match[1];
if (!isValidAgent(agentToken)) {
const agentStart = lineText.indexOf(agentToken);
const agentEnd = agentStart + agentToken.length;
diagnostics.push(
new vscode.Diagnostic(
new vscode.Range(lineNum, agentStart, lineNum, agentEnd),
"Invalid agent token (checksum or field error)",
vscode.DiagnosticSeverity.Error
)
);
}
}
function updateDiagnostics(doc, diagnosticCollection) {
const diagnostics = [];
for (let i = 0; i < doc.lineCount; i++) {
const lineText = doc.lineAt(i).text;
lintUcxlLine(lineText, i, diagnostics);
}
diagnosticCollection.set(doc.uri, diagnostics);
}
vscode.languages.registerHoverProvider("ucxl", {
provideHover(document, position) {
const wordRange = document.getWordRangeAtPosition(position, /[A-Z0-9]{5}/);
if (!wordRange) return null;
const token = document.getText(wordRange);
if (!isValidAgent(token)) return null;
const agentId = decodeToken(token);
return new vscode.Hover(`**AgentID**: Version=${agentId.version}, Host=${agentId.hostId}, GPU=${agentId.gpuSlot}`);
}
});
const crypto = require("crypto");
const CROCKFORD_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
const VERSION_BITS = 3;
const HOST_ID_BITS = 10;
const GPU_SLOT_BITS = 4;
const RESERVED_BITS = 2;
const CHECKSUM_BITS = 6;
const PREFIX_BITS = VERSION_BITS + HOST_ID_BITS + GPU_SLOT_BITS + RESERVED_BITS; // 19
const TOTAL_BITS = PREFIX_BITS + CHECKSUM_BITS; // 25
const MAX_HOST_ID = (1 << HOST_ID_BITS) - 1;
const MAX_GPU_SLOT = (1 << GPU_SLOT_BITS) - 1;
// -------------------
// Base32 helpers
// -------------------
function intToBase32(n, length) {
let chars = Array(length).fill('0');
for (let i = length - 1; i >= 0; i--) {
chars[i] = CROCKFORD_ALPHABET[n & 0x1F];
n >>= 5;
}
return chars.join('');
}
function base32ToInt(s) {
const charMap = {};
for (let i = 0; i < CROCKFORD_ALPHABET.length; i++) {
charMap[CROCKFORD_ALPHABET[i]] = i;
}
if (s.length !== 5) throw new Error(`token length must be 5, got ${s.length}`);
let n = 0;
for (let ch of s.toUpperCase()) {
if ("ILOU".includes(ch)) throw new Error(`invalid character ${ch}`);
const val = charMap[ch];
if (val === undefined) throw new Error(`invalid character ${ch}`);
n = (n << 5) | val;
}
return n;
}
// -------------------
// SHA256 first bits
// -------------------
function sha256FirstBits(value, bits) {
const bytes = Buffer.from([(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]);
const hash = crypto.createHash('sha256').update(bytes).digest();
return hash[0] >> (8 - bits);
}
// -------------------
// Pack/Unpack
// -------------------
function packFields(version, hostId, gpuSlot, reserved) {
if (version >= (1 << VERSION_BITS)) throw new Error("version out of range");
if (hostId > MAX_HOST_ID) throw new Error("host_id out of range");
if (gpuSlot > MAX_GPU_SLOT) throw new Error("gpu_slot out of range");
if (reserved >= (1 << RESERVED_BITS)) throw new Error("reserved out of range");
let bits = 0;
bits = (bits << VERSION_BITS) | version;
bits = (bits << HOST_ID_BITS) | hostId;
bits = (bits << GPU_SLOT_BITS) | gpuSlot;
bits = (bits << RESERVED_BITS) | reserved;
const checksum = sha256FirstBits(bits, CHECKSUM_BITS);
bits = (bits << CHECKSUM_BITS) | checksum;
if (bits >= (1 << TOTAL_BITS)) throw new Error("packed value exceeds allowed bit length");
return bits;
}
function unpackFields(packed) {
if (packed >= (1 << TOTAL_BITS)) throw new Error("packed value exceeds allowed bit length");
const checksum = packed & ((1 << CHECKSUM_BITS) - 1);
const prefix = packed >> CHECKSUM_BITS;
let tmp = prefix;
const reserved = tmp & ((1 << RESERVED_BITS) - 1); tmp >>= RESERVED_BITS;
const gpuSlot = tmp & ((1 << GPU_SLOT_BITS) - 1); tmp >>= GPU_SLOT_BITS;
const hostId = tmp & ((1 << HOST_ID_BITS) - 1); tmp >>= HOST_ID_BITS;
const version = tmp & ((1 << VERSION_BITS) - 1);
const expected = sha256FirstBits(prefix, CHECKSUM_BITS);
if (expected !== checksum) throw new Error("checksum mismatch");
return { version, hostId, gpuSlot, reserved, checksum };
}
// -------------------
// Encode / Decode Token
// -------------------
function encodeToken(version, hostId, gpuSlot, reserved) {
const packed = packFields(version, hostId, gpuSlot, reserved);
return intToBase32(packed, 5);
}
function decodeToken(token) {
const packed = base32ToInt(token);
return unpackFields(packed);
}
// -------------------
// AgentID validator
// -------------------
function isValidAgent(agent) {
try {
decodeToken(agent);
return true;
} catch (e) {
return false;
}
}
// Example usage:
// console.log(isValidAgent(encodeToken(1, 42, 3, 0))); // true
// console.log(isValidAgent("ABCDE")); // false if checksum fails
function isValidUCXLAddress(address) {
return UCXL_REGEX.test(address);
}
function deactivate() {}
module.exports = { activate, deactivate };