mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 08:27:27 +02:00
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
This commit is contained in:
110
src/services/llm/builtin-tools.ts
Normal file
110
src/services/llm/builtin-tools.ts
Normal file
@@ -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();
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user