UNPKG

oscript-vscode-plugin

Version:

Oscript support for writing Autonomous Agents in VS Code

235 lines (204 loc) 6.56 kB
const { Range, createConnection, TextDocuments, DiagnosticSeverity, ProposedFeatures } = require('vscode-languageserver') const axios = require('axios') const { promisify } = require('util') const obyte = require('obyte') const ValidationUtils = require('ocore/validation_utils') const aaValidation = require('ocore/aa_validation') const parseOjson = require('ocore/formula/parse_ojson') const objectHash = require('ocore/object_hash') const open = require('open') const { inspectRules } = require('./rules') const duplicateChecks = { mainnet: {}, testnet: {} } const documents = new TextDocuments() const connection = createConnection(ProposedFeatures.all) connection.onInitialize((params) => {}) connection.onInitialized(async () => {}) documents.onDidChangeContent(change => { validateTextDocument(change.document) }) documents.onDidOpen(change => { validateTextDocument(change.document) }) function inspectTextDocumentRules (textDocument) { const text = textDocument.getText() const rawParsed = parseOjson.parseOjsonGrammar(text) const checks = inspectRules(rawParsed.results[0]) return checks.map(c => c.toDiagnostic(textDocument)) } async function validateTextDocument (textDocument) { connection.sendRequest('aa-validation-inprogress') const text = textDocument.getText() let diagnostics = [] let parsedOjson try { parsedOjson = await promisify(parseOjson.parse)(text) const template = parsedOjson[1] if ('messages' in template) { const aaAddress = objectHash.getChash160(parsedOjson) const { complexity, count_ops: countOps } = await promisify(aaValidation.validateAADefinition)(parsedOjson) const warnings = inspectTextDocumentRules(textDocument) diagnostics = [...diagnostics, ...warnings] connection.sendRequest('aa-validation-success', { complexity, countOps, aaAddress }) } else { if (ValidationUtils.hasFieldsExcept(template, ['base_aa', 'params'])) { throw new Error('foreign fields in parameterized AA definition') } if (!ValidationUtils.isNonemptyObject(template.params)) { throw new Error('no params in parameterized AA') } if (!ValidationUtils.isValidAddress(template.base_aa)) { throw new Error('base_aa is not a valid address') } } } catch (e) { let message const error = e.message || e let range if (error.match(/at line (\d+) col (\d+)/)) { const match = error.match(/at line (\d+) col (\d+)/) message = error range = Range.create( Number(match[1]) - 1, Number(match[2]) - 1, Number(match[1]) - 1, Number.MAX_VALUE ) } else if (error.match(/^validation of formula ([\s\S]+) failed: ([\s\S]+)/)) { const match = error.match(/^validation of formula ([\s\S]+) failed: ([\s\S]+)/) message = match[2] if (message.indexOf('uninitialized local var') !== -1) { const varMatch = message.match(/uninitialized local var (\w+)$/) const variable = varMatch[1] const start = text.indexOf(match[1]) const varPosition = match[1].search(new RegExp('\\$' + variable + '\\b')) range = Range.create( textDocument.positionAt(start + varPosition), textDocument.positionAt(start + varPosition + variable.length + 1) ) } else { const start = text.indexOf(match[1]) range = Range.create( textDocument.positionAt(start), textDocument.positionAt(start + match[1].length) ) } } else { message = error range = Range.create( 0, 0, Number.MAX_VALUE, Number.MAX_VALUE ) } diagnostics.push({ range, message: message.replace(/\t/g, ' '), source: 'ocore', severity: DiagnosticSeverity.Error }) connection.sendRequest('aa-validation-error', { error }) } connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }) return parsedOjson } function checkDuplicateAgent (ojson, config) { return new Promise((resolve, reject) => { const address = objectHash.getChash160(ojson) const networkKey = config.testnet ? 'testnet' : 'mainnet' if (address in duplicateChecks[networkKey]) { if (duplicateChecks[networkKey][address].error) { reject(new Error(duplicateChecks[networkKey][address].error)) } else { resolve(address) } return } const client = new obyte.Client( config.hub, { testnet: config.testnet } ) client.client.ws.addEventListener('error', (e) => { reject(new Error(`Unable to connect to ${config.hub}`)) }) client.client.ws.addEventListener('open', (e) => { client.api.getDefinition(address, function (err, result) { client.close() if (err) { reject(new Error(`Unable to get definition for ${address}`)) } else if (result) { const msg = `Agent already deployed with address ${address}` duplicateChecks[networkKey][address] = { isDuplicate: true, error: msg } reject(new Error(msg)) } else { resolve(address) } }) }) }) } async function handleCheckDuplicate ({ uri, config }) { const document = documents.get(uri) try { const parsedOjson = await validateTextDocument(document) if (!parsedOjson) { throw new Error('Invalid oscript') } const address = await checkDuplicateAgent(parsedOjson, config) connection.window.showInformationMessage(`Agent is ready for deployment with address ${address}`) } catch (e) { connection.window.showErrorMessage(e.message) } } async function handleDeployAa ({ uri, config }) { const document = documents.get(uri) try { const parsedOjson = await validateTextDocument(document) if (!parsedOjson) { throw new Error('Invalid oscript') } await checkDuplicateAgent(parsedOjson, config) const { data } = await axios.post(`${config.backend}/link`, document.getText(), { headers: { 'Content-Type': 'text/plain' }, responseType: 'json' }) if (!data.shortcode) { throw new Error('Can not generate agent deployment link') } const link = `${config.frontnend}/d/${data.shortcode}` open(link) } catch (e) { connection.window.showErrorMessage(e.message) } } async function handleGetAaAddress ({ uri }) { const document = documents.get(uri) try { const parsedOjson = await validateTextDocument(document) if (!parsedOjson) { throw new Error('Invalid oscript') } return objectHash.getChash160(parsedOjson) } catch (e) { connection.window.showErrorMessage(e.message) } } documents.listen(connection) connection.listen() connection.onRequest('deploy-aa', handleDeployAa) connection.onRequest('get-aa-address', handleGetAaAddress) connection.onRequest('check-duplicate', handleCheckDuplicate)