/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/main.ts var main_exports = {}; __export(main_exports, { default: () => NoteBuddyPlugin }); module.exports = __toCommonJS(main_exports); var import_obsidian4 = require("obsidian"); // src/chat-view.ts var import_obsidian2 = require("obsidian"); // src/service.ts var import_obsidian = require("obsidian"); // src/models.ts var defaultSettings = { serviceUrl: "http://127.0.0.1:4096" }; var MODELS_CACHE_TTL = 5 * 60 * 1e3; // src/service.ts var OpenCodeClient = class _OpenCodeClient { constructor(serviceUrl, defaultModelId, onModelUnavailable) { this.session = null; this.cachedProviders = null; this.providersCacheTime = 0; this.cachedDefaults = null; this.serviceUrl = serviceUrl; this.defaultModelId = defaultModelId; this.onModelUnavailable = onModelUnavailable; } static { this.DEFAULT_REQUEST_TIMEOUT_MS = 1e4; } static { this.MESSAGE_REQUEST_TIMEOUT_MS = 6e4; } async request(options, timeoutMs = _OpenCodeClient.DEFAULT_REQUEST_TIMEOUT_MS) { return await Promise.race([ (0, import_obsidian.requestUrl)(options), new Promise( (_, reject) => setTimeout(() => reject(new Error(`Request timeout after ${timeoutMs / 1e3} seconds`)), timeoutMs) ) ]); } async healthCheck() { const url = `${this.serviceUrl}/global/health`; try { const response = await this.request({ url, method: "GET" }); if (response.status === 200 && response.json.healthy === true) { return { status: "connected" /* Connected */, lastTestTime: Date.now() }; } else { return { status: "disconnected" /* Disconnected */, lastTestTime: Date.now(), lastError: `Service unhealthy or unreachable` }; } } catch (error) { const err = error; return { status: "disconnected" /* Disconnected */, lastTestTime: Date.now(), lastError: err.message || "Unknown error" }; } } async createSession() { const url = `${this.serviceUrl}/session`; try { const response = await this.request({ url, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) }); if (response.status === 200) { const data = response.json; return { sessionID: data.id, createTime: data.time.created, title: data.title }; } if (response.status === 400) { const details = this.formatBadRequest(response.json, response.text); throw new Error(details); } throw new Error(`HTTP ${response.status}: ${response.text}`); } catch (error) { const err = error; throw new Error(`Failed to create session: ${err.message}`); } } formatBadRequest(payload, fallbackText) { if (!payload || typeof payload !== "object") { return `Bad request: ${fallbackText || "Unknown error"}`; } const p = payload; const errText = typeof p.errors === "string" ? p.errors : p.errors ? JSON.stringify(p.errors) : ""; const dataText = p.data ? JSON.stringify(p.data) : ""; const msg = [errText, dataText].filter(Boolean).join(" "); return msg ? `Bad request: ${msg}` : `Bad request: ${fallbackText || "Unknown error"}`; } formatNotFound(payload, fallbackText) { if (!payload || typeof payload !== "object") { return fallbackText || "Not found"; } const p = payload; const name = typeof p.name === "string" ? p.name : ""; const data = p.data ? JSON.stringify(p.data) : ""; return [name, data].filter(Boolean).join(" ") || fallbackText || "Not found"; } async sendMessageInternal(sessionID, message) { const url = `${this.serviceUrl}/session/${sessionID}/message`; try { const response = await this.request( { url, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(message) }, _OpenCodeClient.MESSAGE_REQUEST_TIMEOUT_MS ); if (response.status === 200) { const raw = (response.text ?? "").trim(); if (!raw) { throw new Error("Server returned empty response"); } let data; try { data = JSON.parse(raw); } catch { throw new Error(`Invalid JSON response: ${raw.slice(0, 50)}${raw.length > 50 ? "..." : ""}`); } if (!data || typeof data !== "object" || !Array.isArray(data.parts)) { throw new Error("Invalid response shape (expected {info, parts[]})"); } return data; } if (response.status === 400) { const payload = response.json; throw new Error(this.formatBadRequest(payload, response.text)); } if (response.status === 404) { const payload = response.json; throw new Error(`Session not found: ${this.formatNotFound(payload, response.text)}`); } throw new Error(`HTTP ${response.status}: ${response.text}`); } catch (error) { const err = error; throw new Error(`Failed to send message: ${err.message}`); } } async sendMessage(input) { if (!this.session) { this.session = await this.createSession(); } const message = { parts: [{ type: "text", text: input }] }; if (this.defaultModelId) { const parts = this.defaultModelId.split("/"); if (parts.length === 2) { message.model = { providerID: parts[0], modelID: parts[1] }; } } let response; try { response = await this.sendMessageInternal(this.session.sessionID, message); } catch (error) { const err = error; if (message.model && this.isLikelyModelError(err)) { console.log("[NoteBuddy] Selected model unavailable, falling back to server default"); if (this.onModelUnavailable) { this.onModelUnavailable(); } const fallbackMessage = { parts: message.parts }; response = await this.sendMessageInternal(this.session.sessionID, fallbackMessage); } else { throw error; } } const assistantText = response.parts.filter((part) => part.type === "text").map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n"); return assistantText; } async sendMessageToSession(sessionID, message) { return await this.sendMessageInternal(sessionID, message); } isModelsCacheValid() { if (!this.cachedProviders) { return false; } const now = Date.now(); return now - this.providersCacheTime < MODELS_CACHE_TTL; } clearModelsCache() { this.cachedProviders = null; this.providersCacheTime = 0; this.cachedDefaults = null; } async getCapabilities(forceRefresh = false) { const now = Date.now(); if (!forceRefresh && this.cachedProviders && now - this.providersCacheTime < MODELS_CACHE_TTL) { return this.cachedProviders; } const url = `${this.serviceUrl}/config/providers`; try { const response = await this.request({ url, method: "GET" }); if (response.status === 200) { let data; try { data = response.json; } catch { const preview = (response.text || "").trim().slice(0, 50); if (preview.toLowerCase().startsWith(" ({ id: p.id, name: p.name, models: Object.entries(p.models || {}).map(([id, m]) => ({ id, name: m?.name ?? id })) })); this.cachedProviders = providers; this.providersCacheTime = now; return providers; } else { throw new Error(`HTTP ${response.status}: ${response.text}`); } } catch (error) { const err = error; throw new Error(`Failed to get capabilities: ${err.message}`); } } isLikelyModelError(err) { const msg = (err.message || "").toLowerCase(); return msg.includes("model") && (msg.includes("not found") || msg.includes("invalid") || msg.includes("unknown")); } }; // src/chat-view.ts var VIEW_TYPE_CHAT = "note-buddy-chat-view"; var ChatView = class extends import_obsidian2.ItemView { constructor(leaf, plugin) { super(leaf); this.plugin = plugin; this.messages = []; this.client = new OpenCodeClient( plugin.settings.serviceUrl, plugin.settings.defaultModelId, plugin.onModelUnavailable?.bind(plugin) ); } getViewType() { return VIEW_TYPE_CHAT; } getDisplayText() { return "NoteBuddy Chat"; } async onOpen() { const container = this.containerEl.children[1]; container.empty(); const chatContainer = container.createDiv({ cls: "nb-chat-container" }); chatContainer.style.cssText = ` display: flex; flex-direction: column; height: 100%; gap: 1rem; `; const messagesContainer = chatContainer.createDiv({ cls: "nb-messages-container" }); this.messagesContainer = messagesContainer; messagesContainer.style.cssText = ` flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 1rem; `; this.renderMessages(); const welcomeMessage = messagesContainer.createDiv({ cls: "nb-message nb-message-system" }); welcomeMessage.createSpan({ text: "Welcome to NoteBuddy! Chat with your notes here." }); welcomeMessage.style.cssText = ` padding: 0.75rem; background-color: var(--background-secondary); border-radius: 0.5rem; font-size: 0.9rem; color: var(--text-muted); `; const inputContainer = chatContainer.createDiv({ cls: "nb-input-container" }); inputContainer.style.cssText = ` display: flex; gap: 0.5rem; padding: 1rem; border-top: 1px solid var(--background-modifier-border); `; const textarea = inputContainer.createEl("textarea", { cls: "nb-chat-input", attr: { placeholder: "Type your message..." } }); textarea.style.cssText = ` flex: 1; min-height: 60px; max-height: 150px; padding: 0.5rem; border: 1px solid var(--background-modifier-border); border-radius: 0.375rem; background-color: var(--background-primary); color: var(--text-normal); resize: vertical; font-family: inherit; `; const sendButton = inputContainer.createEl("button", { cls: "nb-send-button", text: "Send" }); sendButton.style.cssText = ` padding: 0.5rem 1rem; background-color: var(--interactive-accent); color: var(--text-on-accent); border: none; border-radius: 0.375rem; cursor: pointer; font-weight: 500; `; const sendMessage = async () => { const message = textarea.value.trim(); if (!message) return; this.messages.push({ kind: "userText", text: message }); this.renderMessages(); textarea.value = ""; try { if (!this.plugin.sessionState) { this.plugin.sessionState = await this.client.createSession(); } const sendData = { parts: [{ type: "text", text: message }] }; if (this.plugin.settings.defaultModelId) { const parts = this.plugin.settings.defaultModelId.split("/"); if (parts.length === 2) { sendData.model = { providerID: parts[0], modelID: parts[1] }; } } let response; try { response = await this.client.sendMessageToSession(this.plugin.sessionState.sessionID, sendData); } catch (sendError) { const err = sendError; if (err.message.includes("Session not found")) { this.plugin.sessionState = await this.client.createSession(); response = await this.client.sendMessageToSession(this.plugin.sessionState.sessionID, sendData); } else { throw sendError; } } for (const part of response.parts) { this.messages.push({ kind: "assistantPart", part }); } this.renderMessages(); this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight; } catch (error) { console.error("[NoteBuddy] Send failed:", error); new import_obsidian2.Notice(`Failed to send message: ${error.message}`); } }; sendButton.onclick = sendMessage; textarea.addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); } async onClose() { console.log("[NoteBuddy] Chat view closed"); } renderMessages() { this.messagesContainer.empty(); if (this.messages.length === 0) { const welcomeMessage = this.messagesContainer.createDiv({ cls: "nb-message nb-message-system" }); welcomeMessage.createSpan({ text: "Welcome to NoteBuddy! Chat with your notes here." }); welcomeMessage.style.cssText = ` padding: 0.75rem; background-color: var(--background-secondary); border-radius: 0.5rem; font-size: 0.9rem; color: var(--text-muted); `; return; } for (const item of this.messages) { if (item.kind === "userText") { const messageEl2 = this.messagesContainer.createDiv({ cls: "nb-message nb-message-user" }); messageEl2.createSpan({ text: item.text }); messageEl2.style.cssText = ` padding: 0.75rem; background-color: var(--background-modifier-accent); border-radius: 0.5rem; font-size: 0.9rem; align-self: flex-end; max-width: 70%; `; continue; } const part = item.part; const messageEl = this.messagesContainer.createDiv({ cls: "nb-message nb-message-assistant" }); messageEl.style.cssText = ` padding: 0.75rem; background-color: var(--background-secondary); border-radius: 0.5rem; font-size: 0.9rem; align-self: flex-start; max-width: 70%; `; switch (part.type) { case "text": messageEl.createSpan({ text: part.text }); break; case "reasoning": messageEl.createSpan({ text: part.text }); break; case "tool": messageEl.createEl("strong", { text: `Tool: ${part.tool}` }); messageEl.createEl("pre", { text: JSON.stringify(part.state ?? {}, null, 2) }); break; case "patch": messageEl.createEl("strong", { text: `Patch: ${part.hash}` }); messageEl.createEl("pre", { text: (part.files || []).join("\n") }); break; case "file": messageEl.createEl("strong", { text: `File: ${part.filename ?? part.url}` }); messageEl.createEl("div", { text: `mime: ${part.mime}` }); messageEl.createEl("div", { text: `url: ${part.url}` }); break; case "agent": messageEl.createEl("strong", { text: `Agent: ${part.name}` }); break; case "step_start": messageEl.createEl("strong", { text: `Step start${part.title ? `: ${part.title}` : ""}` }); break; case "step_finish": messageEl.createEl("strong", { text: `Step finish${part.title ? `: ${part.title}` : ""}` }); break; case "snapshot": messageEl.createEl("strong", { text: "Snapshot" }); messageEl.createEl("pre", { text: JSON.stringify(part, null, 2) }); break; case "retry": messageEl.createEl("strong", { text: "Retry" }); messageEl.createEl("pre", { text: JSON.stringify(part, null, 2) }); break; case "compaction": messageEl.createEl("strong", { text: "Compaction" }); messageEl.createEl("pre", { text: JSON.stringify(part, null, 2) }); break; case "unknown": messageEl.createEl("strong", { text: `Unsupported part type: ${part.originalType}` }); messageEl.createEl("pre", { text: JSON.stringify(part, null, 2) }); break; } } } }; // src/settings.ts var import_obsidian3 = require("obsidian"); var NoteBuddySettingTab = class extends import_obsidian3.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.providers = []; this.isLoadingModels = false; this.plugin = plugin; } async display(useCachedProviders) { const { containerEl } = this; containerEl.empty(); containerEl.createEl("h2", { text: "NoteBuddy Settings" }); if (!useCachedProviders) { try { await this.loadProviders(); } catch { } } new import_obsidian3.Setting(containerEl).setName("Service URL").setDesc("The URL of the OpenCode service (must include protocol, hostname, and port)").addText( (text) => text.setPlaceholder("http://127.0.0.1:4096").setValue(this.plugin.settings.serviceUrl).onChange(async (value) => { if (!value.startsWith("http://") && !value.startsWith("https://")) { new import_obsidian3.Notice("Service URL must start with http:// or https://"); return; } try { const url = new URL(value); if (!url.hostname || url.hostname === "") { new import_obsidian3.Notice("Service URL must include a valid hostname"); return; } if (!url.port || url.port === "") { new import_obsidian3.Notice("Service URL must include a port number"); return; } } catch (e) { new import_obsidian3.Notice("Service URL must be a valid URL format"); return; } this.plugin.settings.serviceUrl = value; await this.plugin.saveSettings(); }) ); new import_obsidian3.Setting(containerEl).setName("Test Connection").setDesc("Test the connection to the service").addButton( (button) => button.setButtonText("Test").setCta().onClick(async () => { const client = new OpenCodeClient(this.plugin.settings.serviceUrl, this.plugin.settings.defaultModelId); const result = await client.healthCheck(); if (result.status === "connected") { new import_obsidian3.Notice("Connection successful!"); } else { new import_obsidian3.Notice(`Connection failed: ${result.lastError}`); } }) ); this.displayModelSelection(containerEl); this.displayRefreshButton(containerEl); } async loadProviders(client, forceRefresh) { if (this.isLoadingModels) { return; } this.isLoadingModels = true; try { const c = client ?? new OpenCodeClient(this.plugin.settings.serviceUrl, this.plugin.settings.defaultModelId); this.providers = await c.getCapabilities(forceRefresh ?? false); } catch (error) { this.providers = []; console.error("[NoteBuddy] Failed to load providers:", error); throw error; } finally { this.isLoadingModels = false; } } displayModelSelection(containerEl) { const modelOptions = {}; modelOptions[""] = "Use server default"; for (const provider of this.providers) { for (const model of provider.models) { const value = `${provider.id}/${model.id}`; modelOptions[value] = `${provider.name} - ${model.name}`; } } const currentValue = this.getCurrentModelSelection(); new import_obsidian3.Setting(containerEl).setName("Default Model").setDesc("Select the default AI model to use for conversations").addDropdown( (dropdown) => dropdown.addOptions(modelOptions).setValue(currentValue).onChange(async (value) => { if (value) { this.plugin.settings.defaultModelId = value; } else { this.plugin.settings.defaultModelId = void 0; } await this.plugin.saveSettings(); }) ); } getCurrentModelSelection() { return this.plugin.settings.defaultModelId || ""; } displayRefreshButton(container) { new import_obsidian3.Setting(container).setName("Refresh Models").setDesc("Reload the list of available models from the service").addButton( (button) => button.setButtonText("Refresh").onClick(async () => { const client = new OpenCodeClient(this.plugin.settings.serviceUrl, this.plugin.settings.defaultModelId); client.clearModelsCache(); try { await this.loadProviders(client, true); new import_obsidian3.Notice("Models refreshed"); await this.display(true); } catch (error) { console.error("[NoteBuddy] Failed to load providers:", error); new import_obsidian3.Notice(`Failed to refresh models: ${error.message}`); await this.display(true); } }) ); } }; // src/main.ts var DEFAULT_SETTINGS = defaultSettings; var NoteBuddyPlugin = class extends import_obsidian4.Plugin { constructor() { super(...arguments); this.ribbonIconEl = null; } async onload() { console.log("Loading NoteBuddy plugin..."); await this.loadSettings(); this.registerView( VIEW_TYPE_CHAT, (leaf) => new ChatView(leaf, this) ); this.addRibbonIcon("bot", "NoteBuddy", () => { this.activateView(); }); this.addCommand({ id: "open-chat-view", name: "Open NoteBuddy Chat", callback: () => { this.activateView(); } }); this.app.workspace.onLayoutReady(() => { this.activateView(); }); this.addSettingTab(new NoteBuddySettingTab(this.app, this)); } onunload() { console.log("Unloading NoteBuddy plugin..."); } async activateView() { const { workspace } = this.app; let leaf = null; const leaves = workspace.getLeavesOfType(VIEW_TYPE_CHAT); if (leaves.length > 0) { leaf = leaves[0]; } else { leaf = workspace.getRightLeaf(false); } if (leaf) { await workspace.setActiveLeaf(leaf); await leaf.setViewState({ type: VIEW_TYPE_CHAT, active: true }); } } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } // Callback for when selected model is unavailable onModelUnavailable() { new import_obsidian4.Notice("Selected model is unavailable. Using server default instead."); } async saveSettings() { await this.saveData(this.settings); } };