Project Files
src / config.ts
import { createConfigSchematics } from "@lmstudio/sdk";
import { homedir } from "os";
import path from "path";
export const defaultPluginSettings = {
// ── Chat / Orchestrator ───────────────────────────────────────────────────
model: "qwen/qwen3.6-35b-a3b",
visionPromotionPersistent: false,
logRequests: false,
debugPromotion: false,
debugChunks: false,
maxImagesPerResponse: 5,
imagePreviewMaxSum: 2560,
imagePreviewQuality: 85,
// ── Global ────────────────────────────────────────────────────────────────
baseUrl: "http://127.0.0.1:1234/v1",
apiKey: "",
PREVIEW_IN_CHAT: false,
embedPngMetadata: true,
HTTP_SERVER_PORT: 54760,
// ── Notes ─────────────────────────────────────────────────────────────────
notesDirectory: "",
autoReadUserDocsGuide: true,
exportOverwrite: false,
exportEmbedImages: false,
exportIncludeThinking: false,
exportIncludeToolCalls: false,
// ── Vision API / Analyse / Annotation ─────────────────────────────────────
qwen3VlModelPath: "",
visionPrompt: "",
serverMaxTokens: 768,
serverTemperature: 0.7,
qwen3VlOdPrompt: [
"Detect objects in the image with strict hierarchical prioritization.",
"",
"PRIORITY 1 (CRITICAL - MUST DETECT FIRST):",
'- You MUST detect "human face" (highest priority if a person is present)',
'- You MUST detect "person" (if no face is clearly visible or if the person is the main subject)',
"",
"PRIORITY 2 (MAIN SUBJECT / HERO ELEMENT):",
"- The most visually prominent object or subject that is NOT part of the background.",
"- Use specific, concrete labels (e.g., 'red car', 'fluffy owl toy').",
"- Avoid generic terms like 'object' or 'thing'.",
"",
"PRIORITY 3 (CONTEXTUAL BACKGROUND ELEMENTS):",
"- Only detect background elements if they are significant to the scene composition OR if the main subject is interacting with them.",
"- Do not detect minor or redundant background details.",
"",
"PRIORITY 4 (FOCUSSED MAIN SUBJECT / HERO ELEMENT):",
"- All visible body parts (hands, feet, arms, legs).",
"- Elements of the face, as far as clearly detectable and focussed on close-ups: nose, mouth, left and right eyes, eyebrows and ears",
"- anatomical details, as far as recognizable as \\\"focussed\\\" or \\\"prominent\\\" (e.g., 'iris', 'pupil', 'eyelid')",
"",
"RULES:",
"- Maximum 16 objects total.",
"- Each bounding box must be unique and non-redundant.",
"- For clothing, name the specific garment (e.g., 'tank top', 'jeans').",
"- For body parts, qualify by position (e.g., 'left hand').",
"- NEVER prioritize background elements over the main subject or human face.",
"- NEVER prioritize anatomical details over general concepts unless they are solely focussed (e.g. only detect 'eyes' unless 'human face' is the dominant part of the image)",
"- If the main subject is a person, focus on the person and their immediate interactions. Ignore background elements unless they are directly involved in the interaction.",
"- If NO person or face is visible, ALWAYS detect Priority 2 and Priority 3 subjects regardless.",
].join("\n"),
detectMaxTokens: 2048,
detectTemperature: 0.3,
includeGenerationMetadata: true,
// ── RAG Index ────────────────────────────────────────────────────────────
contentDirectories: [path.join(homedir(), "Documents")],
embeddingModel: "ggml-org/bge-m3-Q8_0-GGUF",
embeddingBaseUrl: "http://127.0.0.1:1234/v1",
embeddingApiKey: "",
chunkSize: 1000,
chunkOverlap: 100,
embeddingBatchSize: 1,
embeddingRequestTimeoutMs: 120000,
retrievalLimit: 5,
watchFiles: true,
extractTables: true,
hybridSearchWeight: 0.3,
languageBiasStrength: 0.3,
remoteSources: [],
remoteFetchTimeoutMs: 10000,
remoteMaxBytes: 15728640,
remoteRefreshMinutes: 1440,
remoteMaxPages: 50,
githubToken: "",
huggingFaceToken: "",
} as const;
export const configSchematics = createConfigSchematics()
.field(
"model",
"string",
{
displayName: "Agent Model",
subtitle:
"Enter the vision model to use as orchestrator. Default: Qwen3.6 35B АЗВ.",
placeholder: "qwen/qwen3.6-35b-a3b",
},
defaultPluginSettings.model
)
.field(
"visionPromotionPersistent",
"boolean",
{
displayName: "Vision Promotion: Persistent",
subtitle:
"ON: promote up to 5 attachments + 4 variants every turn. OFF: promote only when new.",
engineDoesNotSupport: true,
},
defaultPluginSettings.visionPromotionPersistent
)
.field(
"logRequests",
"boolean",
{
displayName: "Debug: Log requests/response",
subtitle: "Logs full request/response JSON; may include sensitive data.",
engineDoesNotSupport: true,
},
defaultPluginSettings.logRequests
)
.field(
"debugPromotion",
"boolean",
{
displayName: "Debug: Media promotion",
subtitle: "Verbose logs for media state, previews and cleanup.",
engineDoesNotSupport: true,
},
defaultPluginSettings.debugPromotion
)
.field(
"debugChunks",
"boolean",
{
displayName: "Debug: Stream chunk logs",
subtitle: "Log raw streaming chunks to console (verbose).",
engineDoesNotSupport: true,
},
defaultPluginSettings.debugChunks
)
.field(
"maxImagesPerResponse",
"numeric",
{
int: true,
min: 1,
max: 20,
displayName: "Max Images per Response",
subtitle: "Maximum number of image candidates registered per find_doc call.",
slider: { min: 1, max: 20, step: 1 },
engineDoesNotSupport: true,
},
defaultPluginSettings.maxImagesPerResponse
)
.field(
"imagePreviewMaxSum",
"numeric",
{
int: true,
min: 512,
max: 8192,
displayName: "Image Preview Max Sum (w+h)",
subtitle: "Maximum w+h in pixels for JPEG preview copies. Higher values preserve readability of screenshots. Default: 3072.",
slider: { min: 512, max: 8192, step: 256 },
engineDoesNotSupport: true,
},
defaultPluginSettings.imagePreviewMaxSum
)
.field(
"imagePreviewQuality",
"numeric",
{
int: true,
min: 50,
max: 100,
displayName: "Image Preview Quality",
subtitle: "JPEG quality for preview copies (50–100).",
slider: { min: 50, max: 100, step: 5 },
engineDoesNotSupport: true,
},
defaultPluginSettings.imagePreviewQuality
)
.build();
export const globalConfigSchematics = createConfigSchematics()
.field(
"baseUrl",
"string",
{
displayName: "Chat API server base-URL",
subtitle:
"Default: http://127.0.0.1:1234/v1",
placeholder: "http://127.0.0.1:1234/v1",
},
defaultPluginSettings.baseUrl
)
.field(
"apiKey",
"string",
{
displayName: "(Optional) Chat API Key",
subtitle: "Only needed if your server requires authentication.",
isProtected: true,
placeholder: "sk-...",
},
defaultPluginSettings.apiKey
)
.field(
"PREVIEW_IN_CHAT",
"boolean",
{
displayName: "Simple Previews in Chat",
subtitle: "When enabled, tool responses include client-based image previews.",
engineDoesNotSupport: true,
},
defaultPluginSettings.PREVIEW_IN_CHAT
)
.field(
"HTTP_SERVER_PORT",
"numeric",
{
displayName: "Local HTTP Server Port",
subtitle: "Port for serving generated images over localhost (default: 54760).",
engineDoesNotSupport: true,
},
defaultPluginSettings.HTTP_SERVER_PORT
)
// ── Notes ─────────────────────────────────────────────────────────────────
.field(
"autoReadUserDocsGuide",
"boolean",
{
displayName: "Auto-read user-docs Guide",
subtitle: "Automatically provide USER-DOCS.md as non-persistent user-message context. Copies the bundled guide into Notes Directory on first use if missing.",
engineDoesNotSupport: false,
},
defaultPluginSettings.autoReadUserDocsGuide
)
.field(
"notesDirectory",
"string",
{
displayName: "Notes Directory",
subtitle: "Directory for agent-managed notes (memorize_doc / export_doc). A subfolder 'images' is created automatically.",
placeholder: path.join(homedir(), "Documents", "User-docs"),
},
defaultPluginSettings.notesDirectory
)
.field(
"exportOverwrite",
"boolean",
{
displayName: "Export: Overwrite existing",
subtitle: "Overwrite an existing note file when exporting.",
engineDoesNotSupport: true,
},
defaultPluginSettings.exportOverwrite
)
.field(
"exportEmbedImages",
"boolean",
{
displayName: "Export: Embed images (Base64)",
subtitle: "Inline chat images as data: URIs. When off, images are copied to images/ as files.",
engineDoesNotSupport: true,
},
defaultPluginSettings.exportEmbedImages
)
.field(
"exportIncludeThinking",
"boolean",
{
displayName: "Export: Include thinking blocks",
subtitle: "Include assistant thinking blocks in the exported Markdown.",
engineDoesNotSupport: true,
},
defaultPluginSettings.exportIncludeThinking
)
.field(
"exportIncludeToolCalls",
"boolean",
{
displayName: "Export: Include tool calls",
subtitle: "Include tool call details in the exported Markdown.",
engineDoesNotSupport: true,
},
defaultPluginSettings.exportIncludeToolCalls
)
// ── RAG Index ────────────────────────────────────────────────────────────
.field(
"contentDirectories",
"stringArray",
{
displayName: "Content Directories",
subtitle: "Local directories to index for RAG. Supports TXT, MD, PDF, and ~/.lmstudio/conversations as LM Studio chat history.",
},
[...defaultPluginSettings.contentDirectories]
)
.field(
"embeddingModel",
"string",
{
displayName: "Embedding Model",
subtitle: "Embedding model for find_doc. Recommended: ggml-org/bge-m3-Q8_0-GGUF",
engineDoesNotSupport: false,
},
defaultPluginSettings.embeddingModel
)
.field(
"embeddingBaseUrl",
"string",
{
displayName: "Embedding/Vision API Base URL",
subtitle: "OpenAI-compatible /v1 URL for embeddings. Vision tools use the same server root and call LM Studio's internal /api/v1 vision endpoints. Separate from the agent API.",
placeholder: "http://127.0.0.1:1234/v1",
engineDoesNotSupport: false,
},
defaultPluginSettings.embeddingBaseUrl
)
.field(
"embeddingApiKey",
"string",
{
displayName: "Embedding/Vision API Key",
subtitle: "Optional key for the embedding and Qwen3-VL vision backend. Separate from the agent API key.",
isProtected: true,
placeholder: "sk-...",
engineDoesNotSupport: false,
},
defaultPluginSettings.embeddingApiKey
)
.field(
"chunkSize",
"numeric",
{
int: true,
min: 200,
max: 3000,
displayName: "Chunk Size",
subtitle: "Maximum character count per chunk.",
slider: { min: 200, max: 3000, step: 100 },
engineDoesNotSupport: true,
},
defaultPluginSettings.chunkSize
)
.field(
"chunkOverlap",
"numeric",
{
int: true,
min: 0,
max: 300,
displayName: "Chunk Overlap",
subtitle: "Character overlap between consecutive chunks.",
slider: { min: 0, max: 300, step: 25 },
engineDoesNotSupport: true,
},
defaultPluginSettings.chunkOverlap
)
.field(
"embeddingBatchSize",
"numeric",
{
int: true,
min: 1,
max: 10,
displayName: "Embedding Batch Size",
subtitle: "Chunks per embedding request. Default 1 avoids multi-chunk request payloads in LM Studio server logs.",
slider: { min: 1, max: 10, step: 1 },
engineDoesNotSupport: true,
},
defaultPluginSettings.embeddingBatchSize
)
.field(
"embeddingRequestTimeoutMs",
"numeric",
{
int: true,
min: 30000,
max: 300000,
displayName: "Embedding Request Timeout (ms)",
subtitle: "Timeout for one embedding request. Large docs and local models may need more than 30 seconds.",
slider: { min: 30000, max: 300000, step: 30000 },
engineDoesNotSupport: true,
},
defaultPluginSettings.embeddingRequestTimeoutMs
)
.field(
"retrievalLimit",
"numeric",
{
int: true,
min: 1,
max: 20,
displayName: "Retrieval Limit",
subtitle: "Maximum number of chunks to retrieve per query.",
slider: { min: 1, max: 20, step: 1 },
engineDoesNotSupport: true,
},
defaultPluginSettings.retrievalLimit
)
.field(
"watchFiles",
"boolean",
{
displayName: "Watch Files",
subtitle: "Automatically re-index files when they change.",
engineDoesNotSupport: true,
},
defaultPluginSettings.watchFiles
)
.field(
"extractTables",
"boolean",
{
displayName: "Extract Tables from PDFs",
subtitle: "Use advanced parsing to extract tables from PDF documents.",
engineDoesNotSupport: true,
},
defaultPluginSettings.extractTables
)
.field(
"hybridSearchWeight",
"numeric",
{
min: 0.0,
max: 1.0,
displayName: "Hybrid Search Weight",
subtitle: "Balance between semantic (0) and keyword (1) search.",
slider: { min: 0.0, max: 1.0, step: 0.1 },
engineDoesNotSupport: true,
},
defaultPluginSettings.hybridSearchWeight
)
.field(
"languageBiasStrength",
"numeric",
{
min: 0.0,
max: 1.0,
displayName: "Language Bias Strength",
subtitle: "Softly prefers retrieved chunks in the conversation language. Set to 0 to disable.",
slider: { min: 0.0, max: 1.0, step: 0.1 },
engineDoesNotSupport: true,
},
defaultPluginSettings.languageBiasStrength
)
.field(
"remoteSources",
"stringArray",
{
displayName: "Remote Sources",
subtitle: "Optional sources to index alongside local documents: GitHub/Hugging Face Markdown, HTTPS docs, lmstudio-conversations://, or a specific .conversation.json file.",
engineDoesNotSupport: true,
},
[...defaultPluginSettings.remoteSources]
)
.field(
"remoteFetchTimeoutMs",
"numeric",
{
int: true,
min: 1000,
max: 60000,
displayName: "Remote Fetch Timeout (ms)",
subtitle: "Timeout for fetching each remote document or image.",
slider: { min: 1000, max: 60000, step: 1000 },
engineDoesNotSupport: true,
},
defaultPluginSettings.remoteFetchTimeoutMs
)
.field(
"remoteMaxBytes",
"numeric",
{
int: true,
min: 1024,
max: 52428800,
displayName: "Remote Max Bytes",
subtitle: "Maximum bytes per fetched remote document or image.",
slider: { min: 1024, max: 52428800, step: 1048576 },
engineDoesNotSupport: true,
},
defaultPluginSettings.remoteMaxBytes
)
.field(
"remoteRefreshMinutes",
"numeric",
{
int: true,
min: 1,
max: 10080,
displayName: "Remote Refresh Minutes",
subtitle: "Refresh interval for remote source metadata. Reserved for adapter cache refresh.",
slider: { min: 1, max: 10080, step: 60 },
engineDoesNotSupport: true,
},
defaultPluginSettings.remoteRefreshMinutes
)
.field(
"remoteMaxPages",
"numeric",
{
int: true,
min: 1,
max: 500,
displayName: "Remote Max Pages",
subtitle: "Maximum pages/files to enumerate for HTTPS docs, GitHub/Hugging Face docs, or LM Studio conversations.",
slider: { min: 1, max: 500, step: 10 },
engineDoesNotSupport: true,
},
defaultPluginSettings.remoteMaxPages
)
.field(
"githubToken",
"string",
{
displayName: "GitHub Token",
subtitle: "Optional token for private GitHub docs or higher rate limits.",
isProtected: true,
placeholder: "github_pat_...",
engineDoesNotSupport: true,
},
defaultPluginSettings.githubToken
)
.field(
"huggingFaceToken",
"string",
{
displayName: "Hugging Face Token",
subtitle: "Optional token for private or gated Hugging Face repos.",
isProtected: true,
placeholder: "hf_...",
engineDoesNotSupport: true,
},
defaultPluginSettings.huggingFaceToken
)
// ── Vision API / Analyse / Annotation ─────────────────────────────────────
.field(
"qwen3VlModelPath",
"string",
{
displayName: "Qwen3-VL: Model Key",
subtitle: "LM Studio model key for API-based analyse_image and annotate_image inference. Must be vision-capable.",
placeholder: "qwen/qwen3-vl-8b",
engineDoesNotSupport: false,
},
defaultPluginSettings.qwen3VlModelPath
)
.field(
"visionPrompt",
"string",
{
displayName: "Vision Prompt",
subtitle: "Default prompt sent to the vision model when the agent does not supply one. Leave empty to disable automatic visual description.",
placeholder: "Analyze this image based strictly on what is directly visible. Do not infer, assume, or complete information that is not present.",
isParagraph: true,
},
defaultPluginSettings.visionPrompt
)
.field(
"serverMaxTokens",
"numeric",
{
displayName: "Vision API: Max Tokens",
subtitle: "Maximum response length in tokens (1–4096). Default: 768.",
engineDoesNotSupport: true,
},
defaultPluginSettings.serverMaxTokens
)
.field(
"serverTemperature",
"numeric",
{
displayName: "Vision API: Temperature",
subtitle: "Sampling temperature (0.0–2.0). Default: 0.7.",
engineDoesNotSupport: true,
},
defaultPluginSettings.serverTemperature
)
.field(
"qwen3VlOdPrompt",
"string",
{
displayName: "Qwen3-VL: Object Detection Prompt",
subtitle: "Instruction sent to Qwen3-VL for default object detection. Leave empty to use the built-in default.",
placeholder: "",
isParagraph: true,
engineDoesNotSupport: false,
},
defaultPluginSettings.qwen3VlOdPrompt
)
.field(
"detectMaxTokens",
"numeric",
{
displayName: "Vision API Detect: Max Tokens",
subtitle: "Maximum response length in tokens for object detection (1–4096). Default: 2048.",
engineDoesNotSupport: true,
},
defaultPluginSettings.detectMaxTokens
)
.field(
"detectTemperature",
"numeric",
{
displayName: "Vision API Detect: Temperature",
subtitle: "Sampling temperature for object detection (0.0–2.0). Default: 0.3.",
engineDoesNotSupport: true,
},
defaultPluginSettings.detectTemperature
)
.field(
"includeGenerationMetadata",
"boolean",
{
displayName: "Include Generation Metadata",
subtitle: "When enabled, Draw Things generation parameters (prompt, model, sampler, seed, …) embedded in PNG files are appended to each analysis result.",
engineDoesNotSupport: true,
},
defaultPluginSettings.includeGenerationMetadata
)
.field(
"embedPngMetadata",
"boolean",
{
displayName: "Embed Metadata in PNGs",
subtitle:
"Write generation parameters (prompt, model, seed, LoRAs, sources) into saved PNGs as XMP metadata. Compatible with draw-things-index.",
engineDoesNotSupport: true,
},
defaultPluginSettings.embedPngMetadata
)
.build();