@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
95 lines • 4.26 kB
JavaScript
import { defineTool } from '../../ai-tool-ref.js';
import { documentIdOf } from '../vector-store.js';
function isInput(value) {
return (typeof value === 'object' &&
value !== null &&
typeof value.documentId === 'string' &&
typeof value.question === 'string');
}
const inputSchema = {
'~standard': {
version: 1,
vendor: 'adonis-agora-agent',
validate(value) {
return isInput(value)
? { value: { documentId: value.documentId, question: value.question } }
: {
issues: [
{ message: 'navigate_document expects { documentId: string, question: string }' },
],
};
},
},
};
/**
* The agentic half of two-stage retrieval: `navigate_document(documentId, question)`, a `read`-kind
* tool the model calls **after** a search put a long document among its hits — it walks that
* document's table of contents to the sections that answer and returns their text, with the path and
* reasoning (the citation surface: section titles and page ranges).
*
* `documentId` may also be a chunk id from a search result (`doc#12`); the chunk suffix is dropped.
* A document with no tree answers `found: false` with a note, so the model falls back to the
* passages it already has instead of retrying.
*
* Returns a {@link defineTool} tool: export it from an `app/agent_tools` module, or pass it to
* `defineConfig({ tools })`.
*
* ```ts
* export const navigateDocument = createNavigateDocumentTool(navigator, {
* filter: (ctx) => ({ tenant: ctx.actor.tenantRef }),
* })
* ```
*/
export function createNavigateDocumentTool(navigator, options = {}) {
return defineTool({
name: options.name ?? 'navigate_document',
kind: 'read',
description: options.description ??
'Read inside one long document (regulation, contract, filing, manual) by navigating its table of contents to the sections that answer a question. Use it after a search returned a long document: pass that document id and the question. Returns the matching sections with titles and page ranges for citation.',
input: inputSchema,
...(options.roles !== undefined ? { roles: options.roles } : {}),
...(options.ability !== undefined ? { ability: options.ability } : {}),
}, async (input, ctx) => {
if (!isInput(input)) {
throw new Error('navigate_document expects { documentId: string, question: string }');
}
const filter = options.filter?.(ctx);
const navigateOptions = {
...(filter !== undefined ? { filter } : {}),
...(options.budget !== undefined ? { budget: options.budget } : {}),
};
let documentId = input.documentId.trim();
let result = await navigator.navigate(input.question, [documentId], navigateOptions);
let navigation = result.documents[0];
if (navigation?.stoppedBy === 'not-found') {
const bare = documentIdOf(documentId.replace(/#node:\d+$/, ''));
if (bare !== documentId) {
documentId = bare;
result = await navigator.navigate(input.question, [documentId], navigateOptions);
navigation = result.documents[0];
}
}
if (navigation === undefined || navigation.stoppedBy === 'not-found') {
return {
documentId,
found: false,
passages: [],
nodes: [],
steps: [],
note: 'This document has no navigable table of contents (it is short, not indexed, or not accessible). Use the search passages instead.',
};
}
return {
documentId,
found: true,
passages: result.passages,
nodes: navigation.nodes,
steps: navigation.steps,
...(navigation.stoppedBy !== undefined ? { stoppedBy: navigation.stoppedBy } : {}),
...(navigation.nodes.length === 0
? { note: 'No section of this document looked relevant to the question.' }
: {}),
};
});
}
//# sourceMappingURL=navigate-tool.js.map