import { OmitKeys } from '../../../../../ClientUtils'; import { DocumentOptions } from '../../../../documents/Documents'; import { Parameter, ParametersType, supportedDocTypes, ToolInfo } from '../types/tool_types'; import { Observation } from '../types/types'; import { AgentDocumentManager } from '../utils/AgentDocumentManager'; import { BaseTool } from './BaseTool'; // Define the parameters for the DocumentMetadataTool const parameterDefinitions: ReadonlyArray = [ { name: 'action', type: 'string', required: true, description: 'The action to perform: "get" to retrieve metadata, "edit" to modify metadata, "getFieldOptions" to retrieve all available field options, or "create" to create a new document', }, { name: 'documentId', type: 'string', required: false, description: 'The ID of the document to get or edit metadata for. Required for "edit", optional for "get", ignored for "getFieldOptions", and "create"', }, { name: 'fieldEdits', type: 'string', required: false, description: `JSON array of field edits for editing fields. Each item should have fieldName and fieldValue. For single field edits, use an array with one item. fieldName values MUST be in this list: [${Object.keys(DocumentOptions)}]. Example: [{"fieldName":"layout_autoHeight","fieldValue":false},{"fieldName":"height","fieldValue":300}]`, //Chat is not honoring restrictions to doc option fields }, { name: 'title', type: 'string', required: false, description: 'The title of the document to create. Required for "create" action', }, { name: 'data', type: 'string', required: false, description: 'The data content for the document to create. Required for "create" action', }, { name: 'doc_type', type: 'string', required: false, description: `The type of document to create. Required for "create" action. Options: ${Object.keys(supportedDocTypes).join(',')}`, }, ] as const; type DocumentMetadataToolParamsType = typeof parameterDefinitions; // Detailed description with usage guidelines for the DocumentMetadataTool const toolDescription = `Extracts and modifies metadata from documents in the same Freeform view as the ChatBox, and can create new documents. This tool helps you work with document properties, understand available fields, edit document metadata, and create new documents. The Dash document system organizes fields in two locations: 1. Layout documents: contain visual properties like position, dimensions, and appearance 2. Data documents: contain the actual content and document-specific data This tool provides the following capabilities: - Get metadata from all documents in the current Freeform view - Get metadata from a specific document - Edit metadata fields on documents (in either layout or data documents) - Edit multiple fields at once (useful for updating dependent fields together) - Retrieve all available field options with metadata (IMPORTANT: always call this before editing) - Understand which fields are stored where (layout vs data document) - Get detailed information about all available document fields - Support for all value types: strings, numbers, and booleans - Create new documents with basic properties DOCUMENT CREATION: - Use action="create" to create new documents with a simplified approach - Required parameters: title, data, and doc_type - The tool will create the document with sensible defaults and link it to the current view - After creation, you can use the edit action to update its properties IMPORTANT: Before editing any document metadata, first call 'getFieldOptions' to understand: - Which fields are available - The data type of each field - Special dependencies between fields (like layout_autoHeight and height) - Proper naming conventions (with or without underscores) IMPORTANT: Some fields have dependencies that must be handled for edits to work correctly: - When editing "height", first set "layout_autoHeight" to false (as a boolean value, not a string) - When editing "width", first set "layout_autoWidth" to false (as a boolean value, not a string) - Check document metadata to identify other similar dependencies - All edits are done using the fieldEdits parameter which accepts an array of fields to modify Example: To change document height, disable auto-height and set height in a single operation: {... inputs: { action: "edit", documentId: "doc123", fieldEdits: [ { fieldName: "layout_autoHeight", fieldValue: false }, { fieldName: "height", fieldValue: 300 } ]}} Here is a detailed example: Create a flow-chart of neurotransmitter release at a synapse, and place a notes box directly underneath that explains each step. I will create a DiagramBox containing a Mermaid flow-chart. After that, I'll make a notes document, read the diagram’s metadata, and then move the notes so it lines up beneath the chart. documentMetadata ***Action rules omitted*** Creating a DiagramBox with a Mermaid chart of neurotransmitter release. create Synaptic Release Flowchart graph TD; A(Vesicle docks)-->B(Ca²⁺ influx); B-->C(Vesicle fuses); C-->D(Neurotransmitter released) diagram Document created successfully. Document ID: 9f2c43c8-0a16-4c12-987b-b813d2f7b1ee Type: diagram Title: "Synaptic Release Flowchart" The document has been created with default dimensions and positioning. … Full metadata for the created document: { /* huge JSON omitted for brevity */ } I will create a plain-text notes document that summarises the diagram’s steps. documentMetadata ***Action rules omitted*** Creating a text document for explanatory notes. create Synaptic Release – Notes This diagram shows vesicle docking, calcium-triggered fusion, and neurotransmitter release into the synaptic cleft. note Document created successfully. Document ID: c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f Type: note Title: "Synaptic Release – Notes" … Full metadata for the created document: { /* huge JSON omitted for brevity */ } I'll fetch the diagram’s width, height, and position so I can align the notes box underneath it. documentMetadata ***Action rules omitted*** Getting metadata for the diagram document. get 9f2c43c8-0a16-4c12-987b-b813d2f7b1ee { "x": 100, "y": 100, "width": 600, "height": 300, "layout_autoWidth": false, "layout_autoHeight": false } The diagram ends at y = 100 + 300 = 400. I’ll move the notes to x = 100, y = 420 (a 20-px gap) and expand its width to 600 px. documentMetadata ***Action rules omitted*** Editing the notes document’s position and size. edit c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f [ {"fieldName":"layout_autoWidth","fieldValue":false}, {"fieldName":"width","fieldValue":600}, {"fieldName":"x","fieldValue":100}, {"fieldName":"y","fieldValue":420} ] Successfully edited 4 fields on document c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f. New position: (x:100, y:420) Width:600 Both documents are now created and perfectly aligned; I can summarise the result. A **DiagramBox** depicting neurotransmitter release was created at **(100 px, 100 px)**, sized **600 × 300 px**. A **notes document** was then resized to **600 px** wide and moved to **(100 px, 420 px)**—20 px below the diagram—to keep the explanation directly beneath the visualization. This layout ensures viewers can read the synopsis while referring to the flow-chart above. Would you like to tweak the diagram’s styling (e.g., colours or fonts)? Should I link external references or papers in the notes? Do you want similar diagrams for other neural processes? The assistant used **DocumentMetadataTool** four times: 1) **create** DiagramBox → 2) **create** notes document → 3) **get** diagram metadata → 4) **edit** notes position/size. This demonstrates creating, inspecting, and aligning documents within a Freeform view. Mermaid mindmaps are hierarchical diagrams used to visually organize ideas. Nodes are created using indentation to show parent-child relationships. This is an experimental feature in Mermaid and may change in future versions. mindmap declares the diagram. Indentation determines the hierarchy. Each level must be indented more than its parent. Nodes can be styled with various shapes similar to flowchart syntax. id[Square Text] id(Rounded Square) id((Circle)) id))Bang(( id)Cloud( id{{Hexagon}} Default shape without any brackets Nodes can include icons using the ::icon(class) syntax. Icon fonts must be included by the site administrator for proper rendering. Add custom styling classes using :::. Classes must be defined in your website or application CSS. Supports markdown-style strings for rich text, line breaks, and auto-wrapping. Indentation is relative, not absolute — Mermaid will infer hierarchy based on surrounding context even with inconsistent spacing. From Mermaid v11, mindmaps are included natively. For older versions, use external imports with lazy loading. import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; ]]> `; // Extensive usage guidelines for the tool const citationRules = `USAGE GUIDELINES: To GET document metadata: - Use action="get" with optional documentId to return metadata for one or all documents - Returns field values, field definitions, and location information (layout vs data document) To GET ALL FIELD OPTIONS (call this first): - Use action="getFieldOptions" to retrieve metadata about all available document fields - No additional parameters are required - Returns structured metadata with field names, types, descriptions, and dependencies - ALWAYS call this before attempting to edit document metadata - Use this information to understand which fields need special handling To CREATE a new document: - Use action="create" with the following required parameters: - title: The title of the document to create - data: The content data for the document (text content, URL, etc.) - doc_type: The type of document to create (text, web, image, etc.) - fieldEdits: Optional JSON array of fields to set during creation - Example: {...inputs: { action: "create", title: "My Notes", data: "This is the content", doc_type: "text", fieldEdits: [{ fieldName: "text", fieldValue: "Hello world" }] }} - After creation, you can edit the document with more specific properties To EDIT document metadata: - Use action="edit" with required parameters: - documentId: The ID of the document to edit - fieldEdits: JSON array of fields to edit, each with fieldName and fieldValue - The tool will determine the correct document location automatically - Field names can be provided with or without leading underscores (e.g., both "width" and "_width" work) - Common fields like "width" and "height" are automatically mapped to "_width" and "_height" - All value types are supported: strings, numbers, and booleans - The tool will apply the edit to the correct document (layout or data) based on existing fields SPECIAL FIELD HANDLING: - Text fields: When editing the 'text' field, provide simple plain text Example: {...inputs: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "text", fieldValue: "Hello world" }] }} The tool will automatically convert your text to the proper RichTextField format - Width/Height: Set layout_autoHeight/layout_autoWidth to false before editing RECOMMENDED WORKFLOW: 0. Understand the currently available documents that were provided as in the prompt 1. Call action="getFieldOptions" to understand available fields 3. Get document metadata with action="get" to see current values 4. Edit fields with action="edit" using proper dependencies OR 0. Understand the state of the currently available documents and their metadata using action="get" (this includes spacial positioning). 1. Create a new document with action="create" 2. Get its ID from the response 3. Edit the document's properties with action="edit" HANDLING DEPENDENT FIELDS: - When editing some fields, you may need to update related dependent fields - For example, when changing "height", you should also set "layout_autoHeight" to false - Use the fieldEdits parameter to update dependent fields in a single operation: {...inputs: { action: "edit", documentId: "doc123", fieldEdits: [ { fieldName: "layout_autoHeight", fieldValue: false }, { fieldName: "height", fieldValue: 300 } ]}} - Always check for dependent fields that might affect your edits, such as: - height → layout_autoHeight (set to false to allow manual height) - width → layout_autoWidth (set to false to allow manual width) - Other auto-sizing related properties Editing fields follows these rules: 1. First checks if the field exists on the layout document using Doc.Get 2. If it exists on the layout document, it's updated there 3. If it has an underscore prefix (_), it's created/updated on the layout document 4. Otherwise, the field is created/updated on the data document 5. Fields with leading underscores are automatically handled correctly Examples: - To get field options: { action: "getFieldOptions" } - To get all document metadata: { action: "get" } - To get metadata for a specific document: { action: "get", documentId: "doc123" } - To edit a single field: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "backgroundColor", fieldValue: "#ff0000" }] } - To edit a width property: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "width", fieldValue: 300 }] } - To edit text content: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "text", fieldValue: "Simple plain text goes here" }] } - To disable auto-height: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "layout_autoHeight", fieldValue: false }] } - To create a text document: { action: "create", title: "My Notes", data: "This is my note content", doc_type: "text" } - To create a web document: { action: "create", title: "Google", data: "https://www.google.com", doc_type: "web" } - To edit height with its dependent field together: { action: "edit", documentId: "doc123", fieldEdits: [ { fieldName: "layout_autoHeight", fieldValue: false }, { fieldName: "height", fieldValue: 200 } ]} - IMPORTANT: MULTI STEP WORKFLOWS ARE NOT ONLY ALLOWED BUT ENCOURAGED. TAKE THINGS 1 STEP AT A TIME. - IMPORTANT: WHEN CITING A DOCUMENT, MAKE THE CHUNK ID THE DOCUMENT ID. WHENEVER YOU CITE A DOCUMENT, ALWAYS MAKE THE CITATION TYPE "text", THE "direct_text" FIELD BLANK, AND THE "chunk_id" FIELD THE DOCUMENT ID.`; const documentMetadataToolInfo: ToolInfo = { name: 'documentMetadata', description: toolDescription, parameterRules: parameterDefinitions, citationRules: citationRules, }; /** * A tool for extracting and modifying metadata from documents in a Freeform view. * This tool collects metadata from both layout and data documents in a Freeform view * and allows for editing document fields in the correct location. */ export class DocumentMetadataTool extends BaseTool { private _docManager: AgentDocumentManager; constructor(docManager: AgentDocumentManager) { super(documentMetadataToolInfo); this._docManager = docManager; this._docManager.initializeDocuments(); } /** * Executes the document metadata tool * @param args The arguments for the tool * @returns An observation with the results of the tool execution */ async execute(args: ParametersType): Promise { console.log('DocumentMetadataTool: Executing with args:', args); // Find all documents based on current mode (canvas or linked) this._docManager.initializeDocuments(); try { // Validate required input parameters based on action if (!this.inputValidator(args)) { return [ { type: 'text', text: `Error: Invalid or missing parameters for action "${args.action}". ${this.getParameterRequirementsByAction(String(args.action))}`, }, ]; } // Ensure the action is valid and convert to string const action = String(args.action); if (!['get', 'edit', 'getFieldOptions', 'create'].includes(action)) { return [ { type: 'text', text: 'Error: Invalid action. Valid actions are "get", "edit", "getFieldOptions", or "create".', }, ]; } // Safely convert documentId to string or undefined const documentId = args.documentId ? String(args.documentId) : undefined; // Perform the specified action switch (action) { case 'get': { // Get metadata for a specific document or all documents const result = this._docManager.getDocumentMetadata(documentId); console.log('DocumentMetadataTool: Get metadata result:', result); return [ { type: 'text', text: `Document metadata ${documentId ? 'for document ' + documentId : ''} retrieved successfully:\n${JSON.stringify(result, null, 2)}`, }, ]; } case 'edit': { // Edit a specific field on a document if (!documentId) { return [ { type: 'text', text: 'Error: Document ID is required for edit actions.', }, ]; } // Ensure document exists if (!this._docManager.has(documentId)) { return [ { type: 'text', text: `Error: Document with ID ${documentId} not found.`, }, ]; } // Check for fieldEdits parameter if (!args.fieldEdits) { return [ { type: 'text', text: 'Error: fieldEdits is required for edit actions. Please provide a JSON array of field edits.', }, ]; } try { // Parse fieldEdits array const edits = JSON.parse(String(args.fieldEdits)); if (!Array.isArray(edits) || edits.length === 0) { return [ { type: 'text', text: 'Error: fieldEdits must be a non-empty array of field edits.', }, ]; } // Track results for all edits const results: { success: boolean; message: string; fieldName?: string; originalFieldName?: string; newValue?: string | number | boolean | object; warning?: string; }[] = []; let allSuccessful = true; // Process each edit for (const edit of edits) { // Get fieldValue in its original form let fieldValue = edit.fieldValue; // Only convert to string if it's neither boolean nor number if (typeof fieldValue !== 'boolean' && typeof fieldValue !== 'number') { fieldValue = String(fieldValue); } const fieldName = String(edit.fieldName); // Edit the field const result = this._docManager.editDocumentField(documentId, fieldName, fieldValue); console.log(`DocumentMetadataTool: Edit field result for ${fieldName}:`, result); // Add to results results.push(result); // Update success status if (!result.success) { allSuccessful = false; } } // Format response based on results let responseText = ''; if (allSuccessful) { responseText = `Successfully edited ${results.length} fields on document ${documentId}:\n`; results.forEach(result => { responseText += `- Field '${result.originalFieldName}': updated to ${JSON.stringify(result.newValue)}\n`; // Add any warnings if (result.warning) { responseText += ` Warning: ${result.warning}\n`; } }); } else { responseText = `Errors occurred while editing fields on document ${documentId}:\n`; results.forEach(result => { if (result.success) { responseText += `- Field '${result.originalFieldName}': updated to ${JSON.stringify(result.newValue)}\n`; // Add any warnings if (result.warning) { responseText += ` Warning: ${result.warning}\n`; } } else { responseText += `- Error editing '${result.originalFieldName}': ${result.message}\n`; } }); } // Get the updated metadata to return const updatedMetadata = this._docManager.getDocumentMetadata(documentId); return [ { type: 'text', text: `${responseText}\nUpdated metadata:\n${JSON.stringify(updatedMetadata, null, 2)}`, }, ]; } catch (error) { return [ { type: 'text', text: `Error processing fieldEdits: ${error instanceof Error ? error.message : String(error)}`, }, ]; } } case 'getFieldOptions': { // Get all available field options with metadata const fieldOptions = this._docManager.getAllFieldMetadata(); return [ { type: 'text', text: `Document field options retrieved successfully.\nThis information should be consulted before editing document fields to understand available options and dependencies:\n${JSON.stringify(fieldOptions, null, 2)}`, }, ]; } case 'create': { // Create a new document if (!args.title || !args.data || !args.doc_type) { return [ { type: 'text', text: 'Error: Title, data, and doc_type are required for create action.', }, ]; } const docType = String(args.doc_type); const title = String(args.title); const data = String(args.data); const json = typeof args.fieldEdits === 'string' ? JSON.parse(args.fieldEdits) : {}; const docopts = json.length ? (json as Array<{ fieldName: string; fieldValue: string | number | boolean }>).reduce((opts, opt) => { opts[opt.fieldName] = opt.fieldValue; return opts; }, {} as DocumentOptions) : {}; const id = await this._docManager.createDocInDash(docType, data, docopts); if (!id) { return [ { type: 'text', text: 'Error: Failed to create document.', }, ]; } // Get the created document's metadata const createdMetadata = this._docManager.extractDocumentMetadata(id); return [ { type: 'text', text: `Document created successfully. Document ID: ${id} Type: ${docType} Title: "${title}" Options: ${JSON.stringify(OmitKeys(args, ['doc_type', 'data', 'title']).omit, null, 2)} The document has been created with default dimensions and positioning. You can now use the "edit" action to modify additional properties of this document. Next steps: 1. Use the "getFieldOptions" action to understand available editable/addable fields/properties and their dependencies. 2. To modify this document, use: { action: "edit", documentId: "${id}", fieldEdits: [{"fieldName":"property","fieldValue":"value"}] } 3. To add styling, consider setting backgroundColor, fontColor, or other properties 4. For text documents, you can edit the content with: { action: "edit", documentId: "${id}", fieldEdits: [{"fieldName":"text","fieldValue":"New content"}] } Full metadata for the created document: ${JSON.stringify(createdMetadata, null, 2)}`, }, ]; } default: return [ { type: 'text', text: 'Error: Unknown action. Valid actions are "get", "edit", "getFieldOptions", or "create".', }, ]; } } catch (error) { console.error('DocumentMetadataTool execution error:', error); return [ { type: 'text', text: `Error executing DocumentMetadataTool: ${error instanceof Error ? error.message : String(error)}`, }, ]; } } /** * Validates the input parameters for the DocumentMetadataTool * This custom validator allows numbers and booleans to be passed for fieldValue * while maintaining compatibility with the standard validation * * @param params The parameters to validate * @returns True if the parameters are valid, false otherwise */ inputValidator(params: ParametersType): boolean { // Default validation for required fields if (params.action === undefined) { return false; } // For create action, validate required parameters if (params.action === 'create') { return !!(params.title && params.data && params.doc_type); } // For edit action, validate fieldEdits is provided if (params.action === 'edit') { if (!params.documentId || !params.fieldEdits) { return false; } try { // Parse fieldEdits and validate its structure const edits = JSON.parse(String(params.fieldEdits)); // Ensure it's an array if (!Array.isArray(edits)) { console.log('fieldEdits is not an array'); return false; } // Ensure each item has fieldName and fieldValue for (const edit of edits) { if (!edit.fieldName) { console.log('An edit is missing fieldName'); return false; } if (edit.fieldValue === undefined) { console.log('An edit is missing fieldValue'); return false; } } // Everything looks good with fieldEdits return true; } catch (error) { console.log('Error parsing fieldEdits:', error); return false; } } // For get action with documentId, documentId is required if (params.action === 'get' && params.documentId === '') { return false; } // getFieldOptions action doesn't require any additional parameters if (params.action === 'getFieldOptions') { return true; } return true; } /** * Returns the parameter requirements for a specific action * @param action The action to get requirements for * @returns A string describing the required parameters */ private getParameterRequirementsByAction(action?: string): string { if (!action) { return 'Please specify an action: "get", "edit", "getFieldOptions", or "create".'; } switch (action.toLowerCase()) { case 'get': return 'The "get" action accepts an optional documentId parameter.'; case 'edit': return 'The "edit" action requires documentId and fieldEdits parameters. fieldEdits must be a JSON array of field edits.'; case 'getFieldOptions': return 'The "getFieldOptions" action does not require any additional parameters. It returns metadata about all available document fields.'; case 'create': return 'The "create" action requires title, data, and doc_type parameters.'; default: return `Unknown action "${action}". Valid actions are "get", "edit", "getFieldOptions", or "create".`; } } }