From afb0ea1e81d5f5704bfae39f7e735ac1bd8d9b50 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 12:29:35 +0000 Subject: [PATCH] feat: add get_my_profile tool and max iterations limit - Create builtin-tools.ts with get_my_profile tool that returns the logged-in user's Nostr profile metadata - Add MAX_TOOL_ITERATIONS (10) to prevent infinite agentic loops - Auto-register builtin tools on session manager import https://claude.ai/code/session_01HqtD9R33oqfB14Gu1V5wHC --- src/services/llm/builtin-tools.ts | 110 ++++++++++++++++++++++++++++ src/services/llm/session-manager.ts | 15 ++++ 2 files changed, 125 insertions(+) create mode 100644 src/services/llm/builtin-tools.ts diff --git a/src/services/llm/builtin-tools.ts b/src/services/llm/builtin-tools.ts new file mode 100644 index 0000000..fe9f913 --- /dev/null +++ b/src/services/llm/builtin-tools.ts @@ -0,0 +1,110 @@ +/** + * Built-in Tools for AI Chat + * + * These tools are automatically registered with the tool registry. + */ + +import { firstValueFrom, filter, timeout, catchError, of } from "rxjs"; +import { getProfileContent } from "applesauce-core/helpers"; +import accounts from "@/services/accounts"; +import eventStore from "@/services/event-store"; +import { toolRegistry, type Tool } from "./tools"; + +// ───────────────────────────────────────────────────────────── +// get_my_profile - Returns the logged-in user's profile +// ───────────────────────────────────────────────────────────── + +const getMyProfileTool: Tool = { + name: "get_my_profile", + description: + "Get the profile information of the currently logged-in user. Returns their display name, about, picture, and other metadata. Returns an error if no user is logged in.", + parameters: { + type: "object", + properties: {}, + required: [], + }, + async execute(_args, _context) { + // Get the active account + const account = accounts.active$.getValue(); + + if (!account) { + return { + success: false, + content: "", + error: "No user is currently logged in", + }; + } + + const pubkey = account.pubkey; + + try { + // Try to get the profile from the event store + const profileEvent = await firstValueFrom( + eventStore.replaceable(0, pubkey).pipe( + filter((event) => event !== undefined), + timeout(5000), + catchError(() => of(undefined)), + ), + ); + + if (!profileEvent) { + return { + success: true, + content: JSON.stringify({ + pubkey, + profile: null, + message: "User is logged in but no profile metadata found", + }), + }; + } + + const profile = getProfileContent(profileEvent); + + if (!profile) { + return { + success: true, + content: JSON.stringify({ + pubkey, + profile: null, + message: + "User is logged in but profile metadata could not be parsed", + }), + }; + } + + return { + success: true, + content: JSON.stringify({ + pubkey, + profile: { + name: profile.name, + display_name: profile.display_name, + about: profile.about, + picture: profile.picture, + banner: profile.banner, + website: profile.website, + nip05: profile.nip05, + lud16: profile.lud16, + }, + }), + }; + } catch (error) { + return { + success: false, + content: "", + error: `Failed to fetch profile: ${error instanceof Error ? error.message : "Unknown error"}`, + }; + } + }, +}; + +// ───────────────────────────────────────────────────────────── +// Register all built-in tools +// ───────────────────────────────────────────────────────────── + +export function registerBuiltinTools(): void { + toolRegistry.register(getMyProfileTool); +} + +// Auto-register on import +registerBuiltinTools(); diff --git a/src/services/llm/session-manager.ts b/src/services/llm/session-manager.ts index f2f6ef2..b254f1d 100644 --- a/src/services/llm/session-manager.ts +++ b/src/services/llm/session-manager.ts @@ -14,6 +14,7 @@ import { BehaviorSubject, Subject } from "rxjs"; import db from "@/services/db"; import { providerManager } from "./provider-manager"; import { toolRegistry, executeToolCalls, type ToolContext } from "./tools"; +import "@/services/llm/builtin-tools"; // Register built-in tools import type { ChatSessionState, StreamingUpdateEvent, @@ -31,6 +32,9 @@ import type { // Session cleanup delay (ms) - wait before cleaning up after last subscriber leaves const CLEANUP_DELAY = 5000; +// Maximum tool execution iterations to prevent infinite loops +const MAX_TOOL_ITERATIONS = 10; + class ChatSessionManager { // ───────────────────────────────────────────────────────────── // Reactive State @@ -340,8 +344,19 @@ class ChatSessionManager { // Agentic loop - continue until we get a final response let continueLoop = true; let totalCost = 0; + let iterations = 0; while (continueLoop) { + iterations++; + + // Safety check: prevent infinite loops + if (iterations > MAX_TOOL_ITERATIONS) { + console.warn( + `[SessionManager] Max tool iterations (${MAX_TOOL_ITERATIONS}) reached for conversation ${conversationId}`, + ); + break; + } + // Check if aborted if (abortController.signal.aborted) { throw new DOMException("Aborted", "AbortError");