Files
obsidian_ollama/src/utils.js
T
fegger 771db09d24 Refactor chat view and add conversation state management
Introduce ConversationStateManager to handle short, medium, and long-term
context for improved conversation flow. Update ChatView to use this manager
and refactor input handling to accept values directly for better testability.

Update OllamaClient with non-streaming chat support and improved error
handling for malformed chunks. Enhance vault indexer with caching, better
scoring, and stop word filtering. Refactor main plugin entry point and
semantic cache initialization for robustness.
2026-05-08 11:52:31 +02:00

169 lines
5.7 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logger = exports.LogLevel = void 0;
exports.validateOllamaUrl = validateOllamaUrl;
exports.validateModelName = validateModelName;
exports.validatePluginSettings = validatePluginSettings;
exports.safeParseJson = safeParseJson;
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
LogLevel[LogLevel["INFO"] = 1] = "INFO";
LogLevel[LogLevel["WARN"] = 2] = "WARN";
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
})(LogLevel || (exports.LogLevel = LogLevel = {}));
const SEVERITY_ORDER = {
debug: LogLevel.DEBUG,
info: LogLevel.INFO,
warn: LogLevel.WARN,
error: LogLevel.ERROR,
};
class Logger {
static setLevel(level) {
if (typeof level === 'string') {
const lowerLevel = level.toLowerCase();
Logger.minLevel = SEVERITY_ORDER[lowerLevel] ?? LogLevel.DEBUG;
}
else {
Logger.minLevel = level;
}
}
static debug(message, category = 'general') {
if (LogLevel.DEBUG >= Logger.minLevel) {
console.debug(`[${category}] DEBUG: ${message}`);
}
}
static info(message, category = 'general') {
if (LogLevel.INFO >= Logger.minLevel) {
console.info(`[${category}] INFO: ${message}`);
}
}
static warn(message, category = 'general') {
if (LogLevel.WARN >= Logger.minLevel) {
console.warn(`[${category}] WARN: ${message}`);
}
}
static error(message, category = 'general') {
if (LogLevel.ERROR >= Logger.minLevel) {
console.error(`[${category}] ERROR: ${message}`);
}
}
}
exports.Logger = Logger;
Logger.minLevel = LogLevel.DEBUG;
// ==================== URL & Model Validation ====================
function validateOllamaUrl(url) {
if (typeof url !== 'string' || !url.trim()) {
return { valid: false, error: 'URL cannot be empty' };
}
const trimmedUrl = url.trim();
if (trimmedUrl.endsWith('/')) {
return { valid: false, error: 'URL should not end with a slash' };
}
try {
const parsed = new URL(trimmedUrl);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return { valid: false, error: 'Must be a valid HTTP or HTTPS URL' };
}
return { valid: true };
}
catch {
return { valid: false, error: 'Must be a valid HTTP or HTTPS URL' };
}
}
function validateModelName(model) {
if (typeof model !== 'string') {
return { valid: false, error: 'Model name must be a string' };
}
const trimmedModel = model.trim();
// Explicit check for empty string after trimming
if (!trimmedModel || trimmedModel.length === 0) {
return { valid: false, error: 'Model name cannot be empty' };
}
if (trimmedModel.length < 2) {
return { valid: false, error: 'Model name must be at least 2 characters long' };
}
if (trimmedModel.length > 100) {
return { valid: false, error: 'Model name must be less than 100 characters long' };
}
if (!/^[a-zA-Z0-9._:-]+$/.test(trimmedModel)) {
return {
valid: false,
error: 'Model name can only contain letters, numbers, dots, dashes, underscores, and colons',
};
}
return { valid: true };
}
function validatePluginSettings(settings) {
const errors = [];
const urlValidation = validateOllamaUrl(settings.ollamaUrl);
if (!urlValidation.valid) {
errors.push(`Invalid Ollama URL: ${urlValidation.error}`);
}
const modelValidation = validateModelName(settings.model);
if (!modelValidation.valid) {
errors.push(`Invalid Model Name: ${modelValidation.error}`);
}
return errors;
}
// ==================== Safe JSON Parsing ====================
const MAX_JSON_SIZE = 1000000;
const MAX_JSON_NESTING = 24;
function countNestingDepth(value, depth = 0) {
if (depth > MAX_JSON_NESTING) {
return depth;
}
if (Array.isArray(value)) {
return Math.max(...value.map((item) => countNestingDepth(item, depth + 1)), depth);
}
if (value !== null && typeof value === 'object') {
const entries = Object.values(value);
if (entries.length === 0)
return depth;
return Math.max(...entries.map((item) => countNestingDepth(item, depth + 1)), depth);
}
return depth;
}
function safeParseJson(jsonString) {
if (typeof jsonString !== 'string') {
throw new Error('Input must be a string');
}
if (jsonString.length > MAX_JSON_SIZE) {
throw new Error('JSON input too large');
}
let parsed;
try {
parsed = JSON.parse(jsonString);
}
catch {
throw new Error('Invalid JSON');
}
// Check for dangerous prototype pollution patterns in object keys only
const checkDangerousPatterns = (obj) => {
if (typeof obj !== 'object' || obj === null) {
return false;
}
const dangerousKeys = ['constructor', 'prototype', '__proto__'];
if (dangerousKeys.some((key) => Object.keys(obj).includes(key))) {
return true;
}
// Recursively check nested objects (own properties only)
const record = obj;
for (const key of Object.keys(obj)) {
if (checkDangerousPatterns(record[key])) {
return true;
}
}
return false;
};
if (checkDangerousPatterns(parsed)) {
throw new Error('dangerous code pattern detected');
}
// Check nesting depth
if (countNestingDepth(parsed) > MAX_JSON_NESTING) {
throw new Error('JSON nesting too deep');
}
return parsed;
}
// ==================== Markdown Utilities ====================