Files
openhuman/app/src/services/memorySourcesService.ts
T

227 lines
6.7 KiB
TypeScript

/**
* RPC client for the memory_sources domain.
*
* Wraps `openhuman.memory_sources_*` RPCs so UI components get typed
* responses without knowing the wire shape.
*/
import debug from 'debug';
import { callCoreRpc } from './coreRpcClient';
const log = debug('memory-sources');
export type SourceKind =
| 'composio'
| 'conversation'
| 'folder'
| 'github_repo'
| 'twitter_query'
| 'rss_feed'
| 'web_page';
export interface MemorySourceEntry {
id: string;
kind: SourceKind;
label: string;
enabled: boolean;
toolkit?: string;
connection_id?: string;
path?: string;
glob?: string;
url?: string;
branch?: string;
paths?: string[];
query?: string;
selector?: string;
// Sync limit fields (all optional; omit = use backend default / unlimited)
since_days?: number;
max_items?: number;
max_commits?: number;
max_issues?: number;
max_prs?: number;
sync_depth_days?: number;
max_tokens_per_sync?: number;
max_cost_per_sync_usd?: number;
}
export interface SourceItem {
id: string;
title: string;
updated_at_ms?: number | null;
}
export interface SourceContent {
id: string;
title: string;
body: string;
content_type: 'markdown' | 'html' | 'plaintext';
metadata: Record<string, unknown>;
}
function unwrap<T>(raw: unknown): T {
const obj = raw as Record<string, unknown>;
if (obj && typeof obj === 'object' && 'result' in obj) {
return obj.result as T;
}
return raw as T;
}
export async function listMemorySources(): Promise<MemorySourceEntry[]> {
log('list');
const resp = await callCoreRpc<{ sources: MemorySourceEntry[] }>({
method: 'openhuman.memory_sources_list',
});
const data = unwrap<{ sources: MemorySourceEntry[] }>(resp);
return data.sources ?? [];
}
export async function getMemorySource(id: string): Promise<MemorySourceEntry | null> {
log('get id=%s', id);
const resp = await callCoreRpc<{ source: MemorySourceEntry | null }>({
method: 'openhuman.memory_sources_get',
params: { id },
});
const data = unwrap<{ source: MemorySourceEntry | null }>(resp);
return data.source ?? null;
}
export async function addMemorySource(
params: Omit<MemorySourceEntry, 'id'>
): Promise<MemorySourceEntry> {
log('add kind=%s label=%s', params.kind, params.label);
const resp = await callCoreRpc<{ source: MemorySourceEntry }>({
method: 'openhuman.memory_sources_add',
params,
});
const data = unwrap<{ source: MemorySourceEntry }>(resp);
return data.source;
}
export async function updateMemorySource(
id: string,
patch: Partial<Omit<MemorySourceEntry, 'id' | 'kind'>>
): Promise<MemorySourceEntry> {
log('update id=%s', id);
const resp = await callCoreRpc<{ source: MemorySourceEntry }>({
method: 'openhuman.memory_sources_update',
params: { id, ...patch },
});
const data = unwrap<{ source: MemorySourceEntry }>(resp);
return data.source;
}
export async function removeMemorySource(id: string): Promise<boolean> {
log('remove id=%s', id);
const resp = await callCoreRpc<{ removed: boolean }>({
method: 'openhuman.memory_sources_remove',
params: { id },
});
const data = unwrap<{ removed: boolean }>(resp);
return data.removed;
}
export async function listSourceItems(sourceId: string): Promise<SourceItem[]> {
log('list_items source_id=%s', sourceId);
const resp = await callCoreRpc<{ items: SourceItem[] }>({
method: 'openhuman.memory_sources_list_items',
params: { source_id: sourceId },
});
const data = unwrap<{ items: SourceItem[] }>(resp);
return data.items ?? [];
}
export async function readSourceItem(sourceId: string, itemId: string): Promise<SourceContent> {
log('read_item source_id=%s item_id=%s', sourceId, itemId);
const resp = await callCoreRpc<{ content: SourceContent }>({
method: 'openhuman.memory_sources_read_item',
params: { source_id: sourceId, item_id: itemId },
});
const data = unwrap<{ content: SourceContent }>(resp);
return data.content;
}
export type FreshnessLabel = 'active' | 'recent' | 'idle';
export interface SourceStatus {
source_id: string;
chunks_synced: number;
chunks_pending: number;
last_chunk_at_ms: number | null;
freshness: FreshnessLabel;
}
export async function memorySourcesStatusList(): Promise<SourceStatus[]> {
log('status_list');
const resp = await callCoreRpc<{ statuses: SourceStatus[] }>({
method: 'openhuman.memory_sources_status_list',
});
const data = unwrap<{ statuses: SourceStatus[] }>(resp);
return data.statuses ?? [];
}
export async function syncMemorySource(sourceId: string): Promise<void> {
log('sync source_id=%s', sourceId);
await callCoreRpc<{ requested: boolean }>({
method: 'openhuman.memory_sources_sync',
params: { source_id: sourceId },
});
}
/**
* Toolkit slugs that ship a native memory-sync provider (backend registry —
* `all_providers()`). The Add Source connection picker uses this to disable
* connections whose toolkit can never sync. Maps to
* `openhuman.memory_sources_supported_toolkits`. See issue #3352.
*/
export async function getSupportedToolkits(): Promise<string[]> {
log('supported_toolkits');
const resp = await callCoreRpc<{ toolkits: string[] }>({
method: 'openhuman.memory_sources_supported_toolkits',
});
const data = unwrap<{ toolkits: string[] }>(resp);
return data.toolkits ?? [];
}
export interface ApplyAllInResult {
sources: MemorySourceEntry[];
sync_triggered: number;
}
/**
* Enables every memory source, clears all per-source sync caps, and
* triggers a background sync for each. Equivalent to the UI "All In"
* action. Maps to `openhuman.memory_sources_apply_all_in`.
*/
export async function applyAllIn(): Promise<ApplyAllInResult> {
log('apply_all_in');
const resp = await callCoreRpc<ApplyAllInResult>({
method: 'openhuman.memory_sources_apply_all_in',
});
const data = unwrap<ApplyAllInResult>(resp);
return { sources: data.sources ?? [], sync_triggered: data.sync_triggered ?? 0 };
}
/// i18n keys for each source kind's user-visible label. Resolve via
/// `t(SOURCE_KIND_LABEL_KEYS[kind])` in components — keeping the keys
/// as a constant lets the dialog kind-picker render the same labels
/// without each call site duplicating the switch.
export const SOURCE_KIND_LABEL_KEYS: Record<SourceKind, string> = {
composio: 'memorySources.kind.composio',
conversation: 'memorySources.kind.conversation',
folder: 'memorySources.kind.folder',
github_repo: 'memorySources.kind.github_repo',
twitter_query: 'memorySources.kind.twitter_query',
rss_feed: 'memorySources.kind.rss_feed',
web_page: 'memorySources.kind.web_page',
};
export const SOURCE_KIND_ICONS: Record<SourceKind, string> = {
composio: '🔗',
conversation: '💬',
folder: '📁',
github_repo: '🐙',
twitter_query: '🐦',
rss_feed: '📡',
web_page: '🌐',
};