@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
53 lines • 2.44 kB
JavaScript
const WHITESPACE = /\s/;
/**
* The trigger token the caret currently sits in, or `null`. Scans backwards from the caret and
* stops at the first whitespace, so a token is always one unbroken word: typing a space is what
* ends a command and hands the rest of the line back to the user as prose.
*/
export function findActiveTrigger(text, caret, sources) {
const end = Math.max(0, Math.min(caret, text.length));
for (let index = end - 1; index >= 0; index--) {
const char = text[index];
if (char === undefined || WHITESPACE.test(char)) {
return null;
}
const source = sources.find((candidate) => candidate.trigger === char);
// A trigger that fails its own position rule is just a character in the token — `@ad/x` is one
// mention query, not a command — so the scan continues past it rather than giving up.
if (source && isAllowedAt(text, index, source.position)) {
return { source, index, query: text.slice(index + 1, end) };
}
}
return null;
}
function isAllowedAt(text, index, position) {
if (index === 0) {
return true;
}
if (position === 'start') {
return false;
}
const previous = text[index - 1];
return previous !== undefined && WHITESPACE.test(previous);
}
/** Case-insensitive substring over label and description. The default when a source names none. */
export function filterAutocompleteItems(items, query) {
// An empty needle is a substring of everything, so it needs no branch of its own.
const needle = query.trim().toLowerCase();
return items.filter((item) => item.label.toLowerCase().includes(needle) ||
(item.description?.toLowerCase().includes(needle) ?? false));
}
/**
* The text and caret after accepting `item`. The trigger character stays — `/deploy ` is what the
* user meant to type — and everything after the caret is left untouched, so completing a token in
* the middle of a sentence does not eat the rest of it.
*/
export function applyCompletion(text, caret, match, item) {
const end = Math.max(match.index, Math.min(caret, text.length));
const inserted = `${match.source.trigger}${item.value ?? item.label}${match.source.insertSuffix ?? ' '}`;
return {
text: text.slice(0, match.index) + inserted + text.slice(end),
caret: match.index + inserted.length,
};
}
//# sourceMappingURL=model.js.map