Ollama Integration: - Add providerConfig.js for managing AI provider settings - Add toolConverter.js to convert between Claude and Ollama formats - Add ollama.js API handler with function calling support - Update diagramAI.ts with Ollama models (llama3.1, mistral, qwen2.5) - Route requests to appropriate provider based on selected model - Use 127.0.0.1 to avoid IPv6 resolution issues New modify_connection Tool: - Add modify_connection tool to change connection labels and colors - Support finding connections by label or by from/to entities - Add chatModifyConnection event handler in diagramManager - Clarify in tool descriptions that empty string removes labels 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
246 lines
7.2 KiB
JavaScript
246 lines
7.2 KiB
JavaScript
/**
|
|
* Tool Format Converter
|
|
* Converts between Claude and Ollama tool/function formats
|
|
*/
|
|
|
|
/**
|
|
* Convert Claude tool definition to Ollama function format
|
|
*
|
|
* Claude format:
|
|
* { name: "...", description: "...", input_schema: { type: "object", properties: {...} } }
|
|
*
|
|
* Ollama format:
|
|
* { type: "function", function: { name: "...", description: "...", parameters: {...} } }
|
|
*
|
|
* @param {object} claudeTool - Tool in Claude format
|
|
* @returns {object} Tool in Ollama format
|
|
*/
|
|
export function claudeToolToOllama(claudeTool) {
|
|
return {
|
|
type: "function",
|
|
function: {
|
|
name: claudeTool.name,
|
|
description: claudeTool.description,
|
|
parameters: claudeTool.input_schema
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert array of Claude tools to Ollama format
|
|
* @param {Array} claudeTools - Array of Claude tool definitions
|
|
* @returns {Array} Array of Ollama function definitions
|
|
*/
|
|
export function claudeToolsToOllama(claudeTools) {
|
|
if (!claudeTools || !Array.isArray(claudeTools)) {
|
|
return [];
|
|
}
|
|
return claudeTools.map(claudeToolToOllama);
|
|
}
|
|
|
|
/**
|
|
* Convert Ollama tool call to Claude format
|
|
*
|
|
* Ollama format (in message):
|
|
* { tool_calls: [{ function: { name: "...", arguments: {...} } }] }
|
|
*
|
|
* Claude format:
|
|
* { type: "tool_use", id: "...", name: "...", input: {...} }
|
|
*
|
|
* @param {object} ollamaToolCall - Tool call from Ollama response
|
|
* @param {number} index - Index for generating unique ID
|
|
* @returns {object} Tool call in Claude format
|
|
*/
|
|
export function ollamaToolCallToClaude(ollamaToolCall, index = 0) {
|
|
const func = ollamaToolCall.function;
|
|
|
|
// Parse arguments if it's a string
|
|
let input = func.arguments;
|
|
if (typeof input === 'string') {
|
|
try {
|
|
input = JSON.parse(input);
|
|
} catch (e) {
|
|
console.warn('[ToolConverter] Failed to parse tool arguments:', e);
|
|
input = {};
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: "tool_use",
|
|
id: `toolu_ollama_${Date.now()}_${index}`,
|
|
name: func.name,
|
|
input: input || {}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert Claude tool result to Ollama format
|
|
*
|
|
* Claude format (in messages):
|
|
* { role: "user", content: [{ type: "tool_result", tool_use_id: "...", content: "..." }] }
|
|
*
|
|
* Ollama format:
|
|
* { role: "tool", content: "...", name: "..." }
|
|
*
|
|
* @param {object} claudeToolResult - Tool result in Claude format
|
|
* @param {string} toolName - Name of the tool (from previous tool_use)
|
|
* @returns {object} Tool result in Ollama message format
|
|
*/
|
|
export function claudeToolResultToOllama(claudeToolResult, toolName) {
|
|
let content = claudeToolResult.content;
|
|
|
|
// Stringify if it's an object
|
|
if (typeof content === 'object') {
|
|
content = JSON.stringify(content);
|
|
}
|
|
|
|
return {
|
|
role: "tool",
|
|
content: content,
|
|
name: toolName
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert Claude messages array to Ollama format
|
|
* Handles regular messages and tool result messages
|
|
*
|
|
* @param {Array} claudeMessages - Messages in Claude format
|
|
* @param {string} systemPrompt - System prompt to prepend
|
|
* @returns {Array} Messages in Ollama format
|
|
*/
|
|
export function claudeMessagesToOllama(claudeMessages, systemPrompt) {
|
|
const ollamaMessages = [];
|
|
|
|
// Add system message if provided
|
|
if (systemPrompt) {
|
|
ollamaMessages.push({
|
|
role: "system",
|
|
content: systemPrompt
|
|
});
|
|
}
|
|
|
|
// Track tool names for tool results
|
|
const toolNameMap = new Map();
|
|
|
|
for (const msg of claudeMessages) {
|
|
if (msg.role === 'user') {
|
|
// Check if it's a tool result message
|
|
if (Array.isArray(msg.content)) {
|
|
for (const block of msg.content) {
|
|
if (block.type === 'tool_result') {
|
|
const toolName = toolNameMap.get(block.tool_use_id) || 'unknown';
|
|
ollamaMessages.push(claudeToolResultToOllama(block, toolName));
|
|
} else if (block.type === 'text') {
|
|
ollamaMessages.push({
|
|
role: "user",
|
|
content: block.text
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
ollamaMessages.push({
|
|
role: "user",
|
|
content: msg.content
|
|
});
|
|
}
|
|
} else if (msg.role === 'assistant') {
|
|
// Handle assistant messages with potential tool calls
|
|
if (Array.isArray(msg.content)) {
|
|
let textContent = '';
|
|
const toolCalls = [];
|
|
|
|
for (const block of msg.content) {
|
|
if (block.type === 'text') {
|
|
textContent += block.text;
|
|
} else if (block.type === 'tool_use') {
|
|
// Track tool name for later tool results
|
|
toolNameMap.set(block.id, block.name);
|
|
toolCalls.push({
|
|
function: {
|
|
name: block.name,
|
|
// Ollama expects arguments as object, not string
|
|
arguments: block.input || {}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
const assistantMsg = {
|
|
role: "assistant",
|
|
content: textContent || ""
|
|
};
|
|
|
|
if (toolCalls.length > 0) {
|
|
assistantMsg.tool_calls = toolCalls;
|
|
}
|
|
|
|
ollamaMessages.push(assistantMsg);
|
|
} else {
|
|
ollamaMessages.push({
|
|
role: "assistant",
|
|
content: msg.content
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return ollamaMessages;
|
|
}
|
|
|
|
/**
|
|
* Convert Ollama response to Claude format
|
|
*
|
|
* @param {object} ollamaResponse - Response from Ollama API
|
|
* @returns {object} Response in Claude format
|
|
*/
|
|
export function ollamaResponseToClaude(ollamaResponse) {
|
|
const content = [];
|
|
const message = ollamaResponse.message;
|
|
|
|
// Add text content if present
|
|
if (message.content) {
|
|
content.push({
|
|
type: "text",
|
|
text: message.content
|
|
});
|
|
}
|
|
|
|
// Add tool calls if present
|
|
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
for (let i = 0; i < message.tool_calls.length; i++) {
|
|
content.push(ollamaToolCallToClaude(message.tool_calls[i], i));
|
|
}
|
|
}
|
|
|
|
// Determine stop reason
|
|
let stopReason = "end_turn";
|
|
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
stopReason = "tool_use";
|
|
} else if (ollamaResponse.done_reason === "length") {
|
|
stopReason = "max_tokens";
|
|
}
|
|
|
|
return {
|
|
id: `msg_ollama_${Date.now()}`,
|
|
type: "message",
|
|
role: "assistant",
|
|
content: content,
|
|
model: ollamaResponse.model,
|
|
stop_reason: stopReason,
|
|
usage: {
|
|
input_tokens: ollamaResponse.prompt_eval_count || 0,
|
|
output_tokens: ollamaResponse.eval_count || 0
|
|
}
|
|
};
|
|
}
|
|
|
|
export default {
|
|
claudeToolToOllama,
|
|
claudeToolsToOllama,
|
|
ollamaToolCallToClaude,
|
|
claudeToolResultToOllama,
|
|
claudeMessagesToOllama,
|
|
ollamaResponseToClaude
|
|
};
|