Files
luma/Agent/core/repl.ts
Ole André Vadla Ravnås 93d9554993 agent: Share scope across REPL evaluations
Run each submission through indirect eval so declarations land
on the global object and persist. Rewrite a leading let/const to
var so it survives too, matching the Node REPL.
2026-07-06 18:06:13 +02:00

148 lines
3.3 KiB
TypeScript

import { encodeValue } from "./value.js";
const evalInScope: (code: string) => any = eval;
export function evaluate(
code: string,
{ raw }: { raw: boolean }
): any {
try {
const result = evalInScope(hoistDeclarations(code));
if (raw) {
return result;
}
return encodeValue(result);
} catch (e) {
return encodeValue(e);
}
}
function hoistDeclarations(code: string): string {
return code.replace(/^\s*(let|const)\b/, "var");
}
export interface CompletionItem {
name: string;
callable: boolean;
}
export function complete(
code: string,
cursor: number
): CompletionItem[] {
const context = getContextAtCursor(code, cursor);
const baseExpr = context.baseExpr;
const fragment = context.fragment;
let candidates: CompletionItem[];
if (baseExpr !== null) {
candidates = memberItems(resolveBase(baseExpr));
} else {
candidates = ownItems(globalThis);
}
if (baseExpr === null && fragment === "") {
return [];
}
let filtered = candidates;
if (fragment !== "") {
filtered = candidates.filter(item => item.name.startsWith(fragment));
}
return filtered.slice(0, 256);
}
function memberItems(base: unknown): CompletionItem[] {
if (base === null || base === undefined) {
return [];
}
const seen = new Set<string>();
const items: CompletionItem[] = [];
let object: unknown = base;
while (object !== null && object !== undefined) {
for (const item of ownItems(object)) {
if (seen.has(item.name)) {
continue;
}
seen.add(item.name);
items.push(item);
}
object = Object.getPrototypeOf(object);
}
return items;
}
function ownItems(object: unknown): CompletionItem[] {
try {
return Object.getOwnPropertyNames(object as object).map(raw => {
const name = String(raw);
const descriptor = Object.getOwnPropertyDescriptor(object as object, name);
return { name, callable: typeof descriptor?.value === "function" };
});
} catch {
return [];
}
}
interface CompletionContext {
baseExpr: string | null;
fragment: string;
}
function getContextAtCursor(code: string, cursor: number): CompletionContext {
const before = code.slice(0, cursor);
let i = before.length - 1;
while (i >= 0) {
const ch = before[i];
if (!/[A-Za-z0-9_$\\.]/.test(ch)) {
break;
}
i -= 1;
}
const token = before.slice(i + 1);
if (token === "") {
return { baseExpr: null, fragment: "" };
}
const dotIndex = token.lastIndexOf(".");
if (dotIndex === -1) {
return {
baseExpr: null,
fragment: token
};
}
const baseExpr = token.slice(0, dotIndex);
const fragment = token.slice(dotIndex + 1);
return {
baseExpr,
fragment
};
}
function resolveBase(baseExpr: string | null): unknown {
if (baseExpr === null) {
return null;
}
try {
const base = evalInScope(baseExpr);
if (base === null || base === undefined) {
return null;
}
return base;
} catch {
return null;
}
}