@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
81 lines • 3.17 kB
JavaScript
import { useCallback, useEffect, useRef, useState } from 'react';
import { useAgentBackend } from '../provider.js';
import { notifyThreads, onThreadsEvent } from './threads-events.js';
/**
* The actor's conversations, kept current without the host wiring refetches: a chat on the same
* backend reports thread creation and settled runs (refetch) and streamed titles (patched in
* place). Headless — it renders nothing.
*/
export function useThreads(options = {}) {
const { enabled = true } = options;
const backend = useAgentBackend(options.backend);
const [threads, setThreads] = useState([]);
const [isLoading, setIsLoading] = useState(enabled);
const [error, setError] = useState(null);
const threadsRef = useRef(threads);
threadsRef.current = threads;
// Only the newest request may write: a slow response must not overwrite a newer one.
const generation = useRef(0);
const refresh = useCallback(async () => {
const mine = ++generation.current;
setIsLoading(true);
try {
const list = await backend.listThreads();
if (mine !== generation.current)
return;
setThreads(list);
setError(null);
}
catch (caught) {
if (mine !== generation.current)
return;
setError(caught instanceof Error ? caught : new Error(String(caught)));
}
finally {
if (mine === generation.current)
setIsLoading(false);
}
}, [backend]);
useEffect(() => {
if (!enabled)
return;
void refresh();
return onThreadsEvent(backend, (event) => {
if (event.type === 'title') {
setThreads((current) => current.map((thread) => thread.id === event.threadId ? { ...thread, title: event.title } : thread));
}
else if (event.type === 'removed') {
setThreads((current) => current.filter((thread) => thread.id !== event.threadId));
}
else {
void refresh();
}
});
}, [backend, enabled, refresh]);
const rename = useCallback(async (id, title) => {
const before = threadsRef.current;
setThreads((current) => current.map((thread) => (thread.id === id ? { ...thread, title } : thread)));
try {
await backend.updateThread(id, { title });
notifyThreads(backend, { type: 'title', threadId: id, title });
}
catch (caught) {
setThreads(before);
throw caught;
}
}, [backend]);
const remove = useCallback(async (id) => {
const before = threadsRef.current;
setThreads((current) => current.filter((thread) => thread.id !== id));
try {
await backend.deleteThread(id);
notifyThreads(backend, { type: 'removed', threadId: id });
}
catch (caught) {
setThreads(before);
throw caught;
}
}, [backend]);
return { threads, isLoading, error, refresh, rename, remove };
}
//# sourceMappingURL=use-threads.js.map