import libraries, increase file read limit (still low). verify wip

This commit is contained in:
Shivering Ocelot committed 2026-10-09 14:44:23 -05:00
1 parent 80454f9a2a
commit ff65f18f80
3 files changed
+60 -15

No files matched your search

-3
View File
@@ -130,9 +130,6 @@ export default class Horde {
} }
sendAction(action, body = {}) { sendAction(action, body = {}) {
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
return Promise.reject(new Error('WebSocket is not open'));
}
const nonce = this.nextNonce++; const nonce = this.nextNonce++;
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
this.pending.set(nonce, { resolve, reject }); this.pending.set(nonce, { resolve, reject });
+50 -9
View File
@@ -19,7 +19,8 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { register } from 'node:module'; import { registerHooks } from 'node:module';
import { pathToFileURL, fileURLToPath } from 'url';
import Horde from './horde.js'; import Horde from './horde.js';
import run from './run.js'; import run from './run.js';
@@ -28,6 +29,9 @@ const CONFIG_FILE = 'config.json';
const FUNCTIONS_DIR = 'functions'; const FUNCTIONS_DIR = 'functions';
const LIB_DIR = 'lib'; const LIB_DIR = 'lib';
// Prefix used by function modules to import shared libraries from '.gral/lib'.
const LIB_PREFIX = '@lib/';
const DEFAULT_CONFIG = { const DEFAULT_CONFIG = {
host: 'wss://hub.hordelab.com', host: 'wss://hub.hordelab.com',
token: null, token: null,
@@ -43,7 +47,6 @@ if (process.env.HORDE_TOKEN) DEFAULT_CONFIG.token = process.env.HORDE_TOKEN;
/** /**
* Global execution context exposed to function modules as `Gral`. * Global execution context exposed to function modules as `Gral`.
* For now it only carries the current working directory.
*/ */
const Gral = { const Gral = {
path: process.cwd(), path: process.cwd(),
@@ -53,6 +56,49 @@ const Gral = {
global.Gral = Gral; global.Gral = Gral;
/**
* Determine the ESM format for any file living inside the project's
* '.gral' directory, so function and library modules load as modules
* regardless of the surrounding workdir's 'package.json'.
*
* @param {string} url A file URL.
* @returns {?string} 'module' for Gral-owned files, otherwise null.
*/
function gralFormat(url) {
let filePath;
try {
filePath = fileURLToPath(url);
} catch (err) {
return null;
}
const rel = path.relative(path.join(process.cwd(), CONFIG_DIR), filePath);
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return null;
return 'module';
}
/**
* Build the resolve hook used to load Gral modules.
*
* @param {string} root Directory the command was launched in.
* @param {string} libDir Absolute path to '.gral/lib'.
* @returns {object} Argument for `registerHooks`.
*/
function createGralHooks(root, libDir) {
return {
resolve(specifier, context, nextResolve) {
if (specifier.startsWith(LIB_PREFIX)) {
const target = path.join(libDir, specifier.slice(LIB_PREFIX.length));
return { url: pathToFileURL(target).href, format: 'module', shortCircuit: true };
}
const result = nextResolve(specifier, context);
const format = result && result.url ? gralFormat(result.url) : null;
return format ? { ...result, format } : result;
},
};
}
/* ------------------------------------------------------------------ * /* ------------------------------------------------------------------ *
* 3. Config loading * 3. Config loading
* ------------------------------------------------------------------ */ * ------------------------------------------------------------------ */
@@ -215,7 +261,7 @@ async function main(argv) {
// Make '.gral/lib/*' importable from function modules via '@lib/<name>'. // Make '.gral/lib/*' importable from function modules via '@lib/<name>'.
const libDir = path.join(root, CONFIG_DIR, LIB_DIR); const libDir = path.join(root, CONFIG_DIR, LIB_DIR);
await register('./loader.js', import.meta.url, { data: { libDir } }); registerHooks(createGralHooks(root, libDir));
const functions = await loadFunctions(root); const functions = await loadFunctions(root);
Gral.horde = new Horde({host: config.host, token: config.token}); Gral.horde = new Horde({host: config.host, token: config.token});
@@ -253,12 +299,7 @@ async function main(argv) {
return 1; return 1;
} }
if (command.prompt === null) { const input = { prompt: command.prompt || null };
console.error("Error: a prompt is required (-p PROMPT or -f PROMPT_FILE).");
return 1;
}
const input = { prompt: command.prompt };
// Connect before executing // Connect before executing
await Gral.horde.connect(); await Gral.horde.connect();
+10 -3
View File
@@ -16,7 +16,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
const MAX_READ_BYTES = 1024; const MAX_READ_BYTES = 10*1024;
const MAX_ITERATIONS = 20; const MAX_ITERATIONS = 20;
/* ------------------------------------------------------------------ * /* ------------------------------------------------------------------ *
@@ -66,12 +66,13 @@ function readFile(args, { root, scope }) {
let lines = fs.readFileSync(abs, 'utf8').split('\n'); let lines = fs.readFileSync(abs, 'utf8').split('\n');
const from = args.lineStart != null ? args.lineStart - 1 : 0; const from = args.lineStart != null ? args.lineStart - 1 : 0;
const to = args.lineEnd != null ? args.lineEnd : lines.length; const to = args.lineEnd != null ? args.lineEnd : lines.length;
const lineCount = lines.length;
lines = lines.slice(from, to); lines = lines.slice(from, to);
let content = lines.join('\n'); let content = lines.join('\n');
const bytes = Buffer.from(content, 'utf8'); const bytes = Buffer.from(content, 'utf8');
if (bytes.length > MAX_READ_BYTES) { if (bytes.length > MAX_READ_BYTES) {
content = bytes.slice(0, MAX_READ_BYTES).toString('utf8'); throw new Error(`too large: ${bytes.length} bytes requested (max ${MAX_READ_BYTES}, ${lineCount} lines in ${file})`);
} }
return content; return content;
} }
@@ -127,7 +128,7 @@ const DEFAULT_TOOLS = {
'read-file': { 'read-file': {
scope: 'read', scope: 'read',
handler: readFile, handler: readFile,
description: 'Read a file.', description: 'Read up to 10KB from a file, with optional line range.',
parameters: { parameters: {
type: 'object', type: 'object',
properties: { properties: {
@@ -239,6 +240,7 @@ function makeExecutor(bundle, root) {
try { try {
return tool.handler(args || {}, { root, scope: tool.scope }); return tool.handler(args || {}, { root, scope: tool.scope });
} catch (err) { } catch (err) {
console.error(err);
return `Error: ${err.message}`; return `Error: ${err.message}`;
} }
}; };
@@ -355,6 +357,7 @@ async function run({
open = null, open = null,
tools = null, tools = null,
defaultTools = true, defaultTools = true,
verify = null,
} = {}) { } = {}) {
const Gral = global.Gral; const Gral = global.Gral;
const root = Gral && Gral.path ? Gral.path : process.cwd(); const root = Gral && Gral.path ? Gral.path : process.cwd();
@@ -420,6 +423,10 @@ async function run({
if (calls.length) { if (calls.length) {
console.log('called: ' + calls.map(c => c.tool_name)); console.log('called: ' + calls.map(c => c.tool_name));
} }
if (verify && verify()) {
return null;
}
} }
throw new Error(`run: exceeded ${MAX_ITERATIONS} tool-call iterations`); throw new Error(`run: exceeded ${MAX_ITERATIONS} tool-call iterations`);