// src/tools/memory/pin-add.ts // Copyright (C) 2025 DTP Technologies, LLC // All Rights Reserved import type { ToolDefinition } from "../../lib/ai-client.js"; import { DtpTool, type ToolArguments, type ToolContext, } from "../../lib/tool.js"; import { ChatSession, ChatSessionMode } from "../../models/chat-session.js"; const MAX_PIN_CHARS = 8192; export class PinAddTool extends DtpTool { get name(): string { return "PinAddTool"; } get slug(): string { return "pin-add"; } get metadata() { return { name: this.definition.function.name || "pin_add", category: "memory", tags: ["pin", "pinboard", "note", "memory", "context"], modes: [ ChatSessionMode.Plan, ChatSessionMode.Build, ChatSessionMode.Test, ChatSessionMode.Ship, ChatSessionMode.Develop, ], }; } public definition: ToolDefinition = { type: "function", function: { name: "pin_add", description: "Add a note (pin) to the session pinboard. Pins are appended to the system prompt and persist across turns. Use this to store important information, preferences, task specs, URLs, or any context you want to keep available. Total pinboard content is limited to 8192 characters.", parameters: { type: "object", properties: { content: { type: "string", description: "The content of the pin to add. Be concise but include all relevant details. This text will be appended to the system prompt.", }, }, required: ["content"], }, }, }; public async execute( context: ToolContext, args: ToolArguments, ): Promise { const content = args.content as string | undefined; if (!content || content.trim().length === 0) { return this.error("INVALID_PARAMETER", "Pin content must not be empty.", { parameter: "content", recoveryHint: "Provide meaningful content for the pin.", }); } const sessionId = context.session._id.toHexString(); try { const session = await ChatSession.findById(sessionId); if (!session) { return this.error("NOT_FOUND", `Session not found: ${sessionId}`); } const currentTotal = session.pins.reduce( (sum, pin) => sum + pin.content.length, 0, ); if (currentTotal + content.length > MAX_PIN_CHARS) { return this.error( "OPERATION_FAILED", `Pinboard is full (${currentTotal}/${MAX_PIN_CHARS} characters used). Remove one or more pins with pin_remove to make room before adding new pins.`, { recoveryHint: `Current usage: ${currentTotal}/${MAX_PIN_CHARS} characters. New pin requires ${content.length} characters. Remove existing pins first.`, }, ); } session.pins.push({ content: content.trim() }); await session.save(); const pinIndex = session.pins.length - 1; const pinId = session.pins[pinIndex]!._id!.toString(); return this.success( { pinId, content: content.trim(), totalPins: session.pins.length, totalChars: currentTotal + content.trim().length, maxChars: MAX_PIN_CHARS, }, `Pin added to pinboard (${session.pins.length} pins, ${currentTotal + content.trim().length}/${MAX_PIN_CHARS} characters used).`, ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.log.error("Failed to add pin", { sessionId, error: errorMessage }); return this.error( "OPERATION_FAILED", `Failed to add pin: ${errorMessage}`, ); } } } export default new PinAddTool();