- Migrate to LangChain for model abstraction (@langchain/anthropic, @langchain/ollama) - Add custom ChatCloudflare class for Cloudflare Workers AI - Simplify API routes using unified LangChain interface - Add session preferences API for storing user settings - Add connection label preference (ask user once, remember for session) - Add shape modification support (change entity shapes via AI) - Add template setter to DiagramObject for shape changes - Improve entity inference with fuzzy matching - Map colors to 16 toolbox palette colors - Limit conversation history to last 6 messages - Fix model switching to accept display names Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
224 lines
9.4 KiB
JavaScript
224 lines
9.4 KiB
JavaScript
/**
|
|
* LangChain Tool Definitions with Zod Schemas
|
|
*
|
|
* Single source of truth for all diagram AI tools.
|
|
* Uses Zod for type-safe schema definitions.
|
|
* Can be exported to Claude or OpenAI format.
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
|
|
// Position schema (reusable) - from camera/user perspective
|
|
const positionSchema = z.object({
|
|
x: z.number().describe("Left (-) / Right (+) position from camera view"),
|
|
y: z.number().describe("Down (-) / Up (+) position (0 = floor, 1.5 = eye level)"),
|
|
z: z.number().describe("Backward (-) / Forward (+) position from camera view")
|
|
}).optional().describe("3D position from camera perspective. Example: (0, 1.5, 2) = directly in front at eye level");
|
|
|
|
// Scale schema - can be number or object, values are in METERS
|
|
const scaleSchema = z.union([
|
|
z.number().describe("Uniform size in meters (e.g., 1 = 1 meter cube, 0.5 = 50cm cube)"),
|
|
z.object({
|
|
x: z.number().describe("Width in meters"),
|
|
y: z.number().describe("Height in meters"),
|
|
z: z.number().describe("Depth in meters")
|
|
}).describe("Size as {x: width, y: height, z: depth} in meters. Example: {x: 1, y: 0.1, z: 1} = 1m wide, 10cm tall, 1m deep")
|
|
]).optional().describe("Size in METERS. Use {x, y, z} for different width/height/depth.");
|
|
|
|
// Rotation schema - can be number or object
|
|
const rotationSchema = z.union([
|
|
z.number().describe("Y-axis rotation in degrees (e.g., 90 = turn right 90°, -90 = turn left 90°)"),
|
|
z.object({
|
|
x: z.number().describe("Pitch in degrees"),
|
|
y: z.number().describe("Yaw in degrees"),
|
|
z: z.number().describe("Roll in degrees")
|
|
}).describe("Full 3D rotation in degrees as {x, y, z}")
|
|
]).optional().describe("Rotation in degrees. Use a number for Y-axis rotation or {x, y, z} for full 3D rotation.");
|
|
|
|
// Tool definitions with Zod schemas
|
|
export const toolSchemas = {
|
|
create_entity: {
|
|
name: "create_entity",
|
|
description: "Create a 3D shape in the diagram. Use this to add new elements like boxes, spheres, cylinders, etc.",
|
|
schema: z.object({
|
|
shape: z.enum(["box", "sphere", "cylinder", "cone", "plane", "person"])
|
|
.describe("The type of 3D shape to create"),
|
|
color: z.string().optional()
|
|
.describe("Color name (red, blue, green, etc.) or hex code (#ff0000)"),
|
|
text: z.string().optional()
|
|
.describe("Text label to display on or near the entity"),
|
|
position: positionSchema
|
|
})
|
|
},
|
|
|
|
connect_entities: {
|
|
name: "connect_entities",
|
|
description: "Draw a connection line between two entities. Check useDefaultLabels preference first - if not set, ask user if they want default labels on connections.",
|
|
schema: z.object({
|
|
from: z.string().describe("ID or label of the source entity"),
|
|
to: z.string().describe("ID or label of the target entity"),
|
|
label: z.string().optional().describe("Optional label for the connection. If omitted, default label 'X to Y' is used based on useDefaultLabels preference."),
|
|
color: z.string().optional().describe("Color of the connection line")
|
|
})
|
|
},
|
|
|
|
list_entities: {
|
|
name: "list_entities",
|
|
description: "List all entities currently in the diagram. Use this to see what exists before connecting or modifying.",
|
|
schema: z.object({})
|
|
},
|
|
|
|
remove_entity: {
|
|
name: "remove_entity",
|
|
description: "Remove an entity from the diagram by its ID or label.",
|
|
schema: z.object({
|
|
target: z.string().describe("ID or label of the entity to remove")
|
|
})
|
|
},
|
|
|
|
modify_entity: {
|
|
name: "modify_entity",
|
|
description: "CALL THIS TOOL to modify an entity. You MUST call this - describing changes does nothing. Use for: resize, move, rename, recolor, rotate, change shape.",
|
|
schema: z.object({
|
|
target: z.string().describe("Label or ID of entity to modify (e.g., 'CDN', 'Server')"),
|
|
color: z.string().optional().describe("New color hex code from toolbox palette"),
|
|
text: z.string().optional()
|
|
.describe("New label text. Use empty string \"\" to remove the label."),
|
|
shape: z.enum(["box", "sphere", "cylinder", "cone", "plane", "person"]).optional()
|
|
.describe("New shape for the entity"),
|
|
position: positionSchema,
|
|
scale: scaleSchema,
|
|
rotation: rotationSchema
|
|
})
|
|
},
|
|
|
|
modify_connection: {
|
|
name: "modify_connection",
|
|
description: "Modify a connection's label or color. Connections can be identified by their label or by specifying the from/to entities.",
|
|
schema: z.object({
|
|
target: z.string().optional()
|
|
.describe("Label of the connection to modify, or use from/to to identify it"),
|
|
from: z.string().optional()
|
|
.describe("ID or label of the source entity (alternative to target)"),
|
|
to: z.string().optional()
|
|
.describe("ID or label of the destination entity (alternative to target)"),
|
|
text: z.string().optional()
|
|
.describe("New label text for the connection. Use empty string \"\" to remove the label."),
|
|
color: z.string().optional()
|
|
.describe("New color for the connection")
|
|
})
|
|
},
|
|
|
|
clear_diagram: {
|
|
name: "clear_diagram",
|
|
description: "DESTRUCTIVE: Permanently delete ALL entities from the diagram and clear the session. This cannot be undone. IMPORTANT: Before calling this tool, you MUST first ask the user to confirm. Only call this tool with confirmed=true AFTER the user explicitly confirms.",
|
|
schema: z.object({
|
|
confirmed: z.boolean()
|
|
.describe("Must be true to execute. Only set to true after user has explicitly confirmed the deletion.")
|
|
})
|
|
},
|
|
|
|
get_camera_position: {
|
|
name: "get_camera_position",
|
|
description: "Get the current camera/viewer position and orientation in the 3D scene. Use this to understand where the user is looking and to position new entities relative to their view.",
|
|
schema: z.object({})
|
|
},
|
|
|
|
list_models: {
|
|
name: "list_models",
|
|
description: "List all available AI models that can be used for this conversation.",
|
|
schema: z.object({})
|
|
},
|
|
|
|
get_current_model: {
|
|
name: "get_current_model",
|
|
description: "Get information about the currently active AI model.",
|
|
schema: z.object({})
|
|
},
|
|
|
|
set_model: {
|
|
name: "set_model",
|
|
description: "Change the AI model. Use the model name from list_models.",
|
|
schema: z.object({
|
|
model_id: z.string()
|
|
.describe("Model name like 'Claude Opus 4', 'Hermes 2 Pro (CF)', 'Mistral Small 3.1 (CF)', etc.")
|
|
})
|
|
},
|
|
|
|
clear_conversation: {
|
|
name: "clear_conversation",
|
|
description: "Clear the conversation history to start fresh. This preserves the diagram entities but clears chat history.",
|
|
schema: z.object({})
|
|
},
|
|
|
|
search_wikipedia: {
|
|
name: "search_wikipedia",
|
|
description: "Search Wikipedia for information about a topic. Use this to research concepts, architectures, technologies, or anything else that would help create more accurate and detailed diagrams.",
|
|
schema: z.object({
|
|
query: z.string()
|
|
.describe("The topic or concept to search for (e.g., 'microservices architecture', 'neural network', 'kubernetes')")
|
|
})
|
|
},
|
|
|
|
set_connection_label_preference: {
|
|
name: "set_connection_label_preference",
|
|
description: "Set whether connections should have default labels. Call this after asking the user their preference.",
|
|
schema: z.object({
|
|
use_default_labels: z.boolean()
|
|
.describe("true = create labels like 'Server to Database', false = no labels on connections")
|
|
})
|
|
},
|
|
|
|
get_connection_label_preference: {
|
|
name: "get_connection_label_preference",
|
|
description: "Check if the user has set a preference for connection labels. Returns the preference or null if not set.",
|
|
schema: z.object({})
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Convert tool schema to Claude/Anthropic format
|
|
* @param {object} toolDef - Tool definition with name, description, and Zod schema
|
|
* @returns {object} Tool in Claude format
|
|
*/
|
|
function toClaudeFormat(toolDef) {
|
|
return {
|
|
name: toolDef.name,
|
|
description: toolDef.description,
|
|
input_schema: zodToJsonSchema(toolDef.schema, { target: "openApi3" })
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert tool schema to OpenAI/Ollama/Cloudflare format
|
|
* @param {object} toolDef - Tool definition with name, description, and Zod schema
|
|
* @returns {object} Tool in OpenAI function format
|
|
*/
|
|
function toOpenAIFormat(toolDef) {
|
|
return {
|
|
type: "function",
|
|
function: {
|
|
name: toolDef.name,
|
|
description: toolDef.description,
|
|
parameters: zodToJsonSchema(toolDef.schema, { target: "openApi3" })
|
|
}
|
|
};
|
|
}
|
|
|
|
// Export tools in different formats
|
|
export const claudeTools = Object.values(toolSchemas).map(toClaudeFormat);
|
|
export const openAITools = Object.values(toolSchemas).map(toOpenAIFormat);
|
|
|
|
// For backwards compatibility - alias
|
|
export const ollamaTools = openAITools;
|
|
export const cloudflareTools = openAITools;
|
|
|
|
export default {
|
|
toolSchemas,
|
|
claudeTools,
|
|
openAITools,
|
|
ollamaTools,
|
|
cloudflareTools
|
|
};
|