u
This commit is contained in:
@@ -1,8 +1,15 @@
|
|||||||
---
|
---
|
||||||
name: naming-conventions
|
name: naming-conventions-and-style
|
||||||
description: "Use when: improving variable, function, module, or API names; refactoring code for clarity; choosing consistent naming in the dashback backend."
|
description: "Use when: improving variable, function, module, or API names; refactoring code for clarity; choosing consistent naming in the dashback backend."
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Style
|
||||||
|
|
||||||
|
use vs code region markers in code files
|
||||||
|
use:
|
||||||
|
// MARK: calls startApi from ./api/index.js
|
||||||
|
atop important functions
|
||||||
|
|
||||||
# Naming Conventions — dashback
|
# Naming Conventions — dashback
|
||||||
|
|
||||||
Apply these conventions when writing or refactoring code in this project.
|
Apply these conventions when writing or refactoring code in this project.
|
||||||
@@ -22,6 +22,7 @@ process.on('SIGTERM', () => {
|
|||||||
shutdown().then(() => process.exit(0));
|
shutdown().then(() => process.exit(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// MARK: calls startApi from ./api/index.js
|
||||||
startApi().catch((err) => {
|
startApi().catch((err) => {
|
||||||
sysLog.error('Failed to start API:', err.message);
|
sysLog.error('Failed to start API:', err.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { LLMOrbiter, OpenRouterProvider } from 'libllmorbiter';
|
import { LLMOrbiter, OpenRouterProvider } from 'libllmorbiter';
|
||||||
import * as db from '../db/modelconversation.js';
|
import * as db from '../db/modelconversation.js';
|
||||||
import * as dbCompanySetting from '../db/companysetting.js';
|
|
||||||
import { getStore } from '../db/store.js';
|
import { getStore } from '../db/store.js';
|
||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
import { emit as emitCompanyEvent } from '../api/sse.js';
|
import { emit as emitCompanyEvent } from '../api/sse.js';
|
||||||
@@ -8,12 +7,12 @@ import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|||||||
import { resolve, dirname, extname } from 'path';
|
import { resolve, dirname, extname } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { buildSystemPrompt } from './prompts.js';
|
||||||
import { createDashbackRequestTool } from '../tools/dashback-request.js';
|
import { createDashbackRequestTool } from '../tools/dashback-request.js';
|
||||||
import { createAskUserTool } from '../tools/ask-user.js';
|
import { createAskUserTool } from '../tools/ask-user.js';
|
||||||
import { createJavascriptExecuteTool } from '../tools/javascript.js';
|
import { createJavascriptExecuteTool } from '../tools/javascript.js';
|
||||||
import { createResetVfsTool } from '../tools/reset-vfs.js';
|
import { createResetVfsTool } from '../tools/reset-vfs.js';
|
||||||
import { resolveVfsRoot, prepareVfs, getMimeType } from '../tools/vfs.js';
|
import { resolveVfsRoot, prepareVfs, getMimeType } from '../tools/vfs.js';
|
||||||
import { listSkillFiles } from '../tools/skill.js';
|
|
||||||
import { createApiSuggestionsOrIssuesTool } from '../tools/apisuggestionsorissues.js';
|
import { createApiSuggestionsOrIssuesTool } from '../tools/apisuggestionsorissues.js';
|
||||||
import { createFindFilesTool } from '../tools/find-files.js';
|
import { createFindFilesTool } from '../tools/find-files.js';
|
||||||
import { createReadFileTool } from '../tools/read-file.js';
|
import { createReadFileTool } from '../tools/read-file.js';
|
||||||
@@ -31,161 +30,14 @@ import { createNetGrossCalcTool } from '../tools/net-gross-calc.js';
|
|||||||
import { createSetUserRouteTool } from '../tools/set-user-route.js';
|
import { createSetUserRouteTool } from '../tools/set-user-route.js';
|
||||||
import { resolveOpenRouterKey, resolveDefaultModel, resolveModelReasoning, RETIRED_MODELS, hasConfiguredOpenRouterKey } from '../tools/llm-key.js';
|
import { resolveOpenRouterKey, resolveDefaultModel, resolveModelReasoning, RETIRED_MODELS, hasConfiguredOpenRouterKey } from '../tools/llm-key.js';
|
||||||
|
|
||||||
|
// #region Constants
|
||||||
|
|
||||||
const KEEP_ALIVE_MS = 20_000;
|
const KEEP_ALIVE_MS = 20_000;
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the SKILL index block for the system prompt.
|
|
||||||
*
|
|
||||||
* Listing the skills inline means the model knows what guidance exists BEFORE it decides what
|
|
||||||
* to do, so it can reach for `read_file` on the right skill instead of guessing a procedure
|
|
||||||
* from scratch. Only the filename, title and line count are included — the body is read on
|
|
||||||
* demand, which keeps the prompt small no matter how many skills exist.
|
|
||||||
*
|
|
||||||
* @param {string} vfsRoot
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function buildSkillIndex(vfsRoot) {
|
|
||||||
// A null root means the VFS could not be resolved (no company selected, or a store
|
|
||||||
// error). Say so plainly instead of pretending the company has no skills — otherwise the
|
|
||||||
// model would conclude no guidance exists and invent its own procedure.
|
|
||||||
if (!vfsRoot) {
|
|
||||||
return 'AVAILABLE SKILLS: unknown (the virtual filesystem could not be read this turn). Call find_files({ skills: true }) to check.';
|
|
||||||
}
|
|
||||||
|
|
||||||
let skills;
|
// #region Message Helpers
|
||||||
try {
|
|
||||||
skills = listSkillFiles(vfsRoot);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('conversation: failed to list skill files:', err.message);
|
|
||||||
return 'AVAILABLE SKILLS: could not be read this turn. Call find_files({ skills: true }) to check.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skills.length === 0) {
|
|
||||||
return [
|
|
||||||
'AVAILABLE SKILLS: none. This company has no SKILL files yet.',
|
|
||||||
].join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
const width = Math.max(...skills.map((s) => s.path.length));
|
|
||||||
return [
|
|
||||||
`AVAILABLE SKILLS (${skills.length}): stored in the VFS root as flat <slug>.SKILL files.`,
|
|
||||||
'Before starting a task that any of these cover, read the matching skill with read_file and follow it.',
|
|
||||||
'',
|
|
||||||
...skills.map((s) => ` ${s.path.padEnd(width)} ${String(s.lines).padStart(4)} lines | ${s.title}`),
|
|
||||||
].join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* System prompt: base instructions plus the generated API routes reference
|
|
||||||
* (docs/api/routes.md), read on each run so it stays current.
|
|
||||||
*
|
|
||||||
* @param {string} vfsRoot Resolved VFS root for this user's company, used to list SKILL files.
|
|
||||||
* @param {number} [googleAuthId]
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
*/
|
|
||||||
async function buildSystemPrompt(vfsRoot, googleAuthId) {
|
|
||||||
let prefix = '';
|
|
||||||
let suffix = '';
|
|
||||||
if (googleAuthId) {
|
|
||||||
try {
|
|
||||||
const prefixSetting = await dbCompanySetting.getByKey(googleAuthId, 'systemprompt-prefix', { maskWriteonly: false });
|
|
||||||
prefix = prefixSetting?.value?.trim() || '';
|
|
||||||
} catch (err) {
|
|
||||||
console.error('conversation: failed to read systemprompt-prefix:', err.message);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const suffixSetting = await dbCompanySetting.getByKey(googleAuthId, 'systemprompt-suffix', { maskWriteonly: false });
|
|
||||||
suffix = suffixSetting?.value?.trim() || '';
|
|
||||||
} catch (err) {
|
|
||||||
console.error('conversation: failed to read systemprompt-suffix:', err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const today = new Date().toISOString().split('T')[0];
|
|
||||||
const basePrompt = [
|
|
||||||
'QuixPOS Article Management System API Assistant',
|
|
||||||
`Date: ${today}`,
|
|
||||||
'',
|
|
||||||
'TOOLS:',
|
|
||||||
'1. dashback_request: HTTP requests (GET, POST, PUT, PATCH, DELETE) to QuixPOS API.',
|
|
||||||
'2. ask_user: Prompt user for missing decisions or parameters; pauses until answered.',
|
|
||||||
'3. javascript_execute: Sandboxed Node env for computation, binary I/O, aggregation. Assign module.exports. Has fetch("/path"), vfs, sharp, ExcelJS.',
|
|
||||||
'4. find_files: List VFS files (skills=true for SKILL files).',
|
|
||||||
'5. read_file: Read VFS text/CSV/JSON files (seek/limit windowed).',
|
|
||||||
'6. patch: Create/overwrite/replace text in VFS files, incl. SKILL files (only text write path).',
|
|
||||||
'7. search_images: Search Google Images; downloads AVIF candidates to VFS.',
|
|
||||||
'8. analyze_image: Vision analysis of up to 9 images (VFS paths or "image/<hash>"). Always evaluate images with this tool.',
|
|
||||||
'9. web_search: Google web search (timePeriod: "day"|"week" for current events).',
|
|
||||||
'10. url_get: Fetch/scrape web page as markdown.',
|
|
||||||
'11. pdf_read: Convert PDF to layout text ("txt") or VFS page images ("png").',
|
|
||||||
'12. reset_vfs: Delete all non-SKILL VFS files.',
|
|
||||||
'13. rm: Delete a single VFS file (including SKILL files).',
|
|
||||||
'14. apisuggestionsorissues: Report API bugs or missing endpoints.',
|
|
||||||
'15. search_articles: Search by name, brand, EAN, SKU, supplier SKU. Returns full articles window (default 3, max 20), offset, total.',
|
|
||||||
'16. edit_image: Generate/edit images with the company image model (modelslugimage). Supports attachments (low/high quality), sizes 1K/2K/4K, ratios 1:1/4:3/3:4. Output: AVIF in VFS.',
|
|
||||||
'17. ean_checksum: Validate or generate barcode check digits (EAN-13, EAN-8, UPC-A, GTIN-14).',
|
|
||||||
'18. net_gross_calc: Convert gross/net with VAT (default 19%). Returns 2-decimal display values and high-precision net for the API.',
|
|
||||||
'19. set-user-route: Navigate the UI (e.g. "show me the article", "open quote 1001") using the Route Matrix.',
|
|
||||||
'',
|
|
||||||
'VFS & SKILLS:',
|
|
||||||
'- Persistent per-company VFS; root-relative forward-slash paths (e.g. "labels/sheet1.png"). Can host a static website.',
|
|
||||||
'- User uploads go to "attachments/…", tagged in messages as `[ATTACHMENT name= path= type=]`. Inspect with read_file (text/csv/json), pdf_read (PDF), analyze_image (images), javascript_execute + ExcelJS (spreadsheets).',
|
|
||||||
'- Markdown links: internet links are "https://…" or dotted hostnames ("hostname.com/file"); anything else is a VFS path (`[download](report.csv)`).',
|
|
||||||
'- SKILL files: root-only "<slug>.SKILL" markdown (first line = title). Discover via find_files({ skills: true }). ALWAYS read_file the matching skill BEFORE executing a covered task. Propose saving improved procedures as new SKILL files. Exempt from reset_vfs; delete via rm.',
|
|
||||||
'',
|
|
||||||
buildSkillIndex(vfsRoot),
|
|
||||||
'',
|
|
||||||
'RULES:',
|
|
||||||
'- PRICES: user input is GROSS, API requires NET. Always use net_gross_calc (4-6 decimals): net = gross / (1 + taxRate / 100).',
|
|
||||||
'- LABELS (/labeltemplate): Dashback JSON `{ "objects": [ ... ] }`. Do NOT use ZPL.',
|
|
||||||
'- IMAGES: flow search_images → analyze_image({ paths, prompt }) → upload via fetch in javascript_execute. `image/` is READ-ONLY; write generated images elsewhere (e.g. "images/…").',
|
|
||||||
'- OUTPUT: Markdown + emoji. Render VFS images inline ``; other files (PDF, JSON, CSV) need download links.',
|
|
||||||
'- NAVIGATION: when the user asks to view/open a page or entity, call set-user-route with a path from the matrix.',
|
|
||||||
'- API: routes are not inlined. BEFORE dashback_request, read_file the api-<section>.SKILL for exact params and response shapes. `api-binary-streaming-routes.SKILL` covers fetch()-only routes.',
|
|
||||||
'- T* TYPES: `T<n>` (e.g. T2) is shorthand for a reusable object shape. Ids are GLOBAL: the same T<n> always means the same shape in every skill. Each api-<section>.SKILL only lists, in its "Reusable types" block, the T<n> it uses (plus the ones those reference), so a skill shows a subset of the registry, and a T<n> may appear in several skills with an identical definition.',
|
|
||||||
'',
|
|
||||||
'BINARY IMAGE UPLOAD (fetch() in javascript_execute, image/avif buffer body):',
|
|
||||||
'- POST /article/{sku}/image -> 201 { image: { picId, sort, full: { w, h, size }, thumb: { w, h }, hash, thumbHash } }',
|
|
||||||
'- POST /category/{id}/image -> 201 { category: { id, thumbW, thumbH, thumbHash } }',
|
|
||||||
'- POST /brand/{id}/image -> 201 { brand: { id, fullHash, thumbW, thumbH, thumbHash, pics: [...] } }',
|
|
||||||
'',
|
|
||||||
'ROUTE MATRIX (client routes German, API sections English; skills = api-<section>):',
|
|
||||||
'| Destination | Path | Example | API sections |',
|
|
||||||
'|:---|:---|:---|:---|',
|
|
||||||
'| Article Search | `/artikel?target={query}&filter={filter}&offset={n}` | `/artikel?target=kabel` | article, category, brand |',
|
|
||||||
'| Article Detail | `/artikel/${encodeURIComponent(sku)}` | `/artikel/ART-10042` | article, category, brand, attributename, propertyname, unit, supplier, printer, labeltemplate, ledger |',
|
|
||||||
'| Warehouse / Stock | `/artikel/lager` | `/artikel/lager` | warehouse, stock, ledger |',
|
|
||||||
'| Brands | `/artikel/marken` | `/artikel/marken` | brand |',
|
|
||||||
'| Label Templates | `/artikel/vorlagen` | `/artikel/vorlagen` | labeltemplate, printer |',
|
|
||||||
'| Attributes | `/artikel/merkmale` | `/artikel/merkmale` | attributename, attributevalue, propertyname |',
|
|
||||||
'| Category Root | `/kategorien` | `/kategorien` | category |',
|
|
||||||
'| Category Subtree | `/kategorien/${slug1}/${slug2}` (encoded slugs) | `/kategorien/elektronik/kabel` | category, article |',
|
|
||||||
'| Delivery List | `/lieferung` | `/lieferung` | delivery |',
|
|
||||||
'| New Delivery | `/lieferung/neu` | `/lieferung/neu` | delivery, supplier, article, warehouse, stock |',
|
|
||||||
'| Delivery Detail | `/lieferung/${deliveryId}` | `/lieferung/42` | delivery, supplier, ledger |',
|
|
||||||
'| Suppliers | `/lieferung/lieferanten` | `/lieferung/lieferanten` | supplier |',
|
|
||||||
'| Quote List | `/auftrag` | `/auftrag` | quote |',
|
|
||||||
'| New Quote | `/auftrag/neu` | `/auftrag/neu` | quote, entity, article |',
|
|
||||||
'| Quote Detail | `/auftrag/${quoteId}` | `/auftrag/1001` | quote, entity |',
|
|
||||||
'| Order List | `/auftrag/bestellung` | `/auftrag/bestellung` | order |',
|
|
||||||
'| New Order | `/auftrag/bestellung/neu` | `/auftrag/bestellung/neu` | order, entity, article |',
|
|
||||||
'| Order Detail | `/auftrag/bestellung/${orderId}` | `/auftrag/bestellung/2005` | order, entity, account, payment, shipping, package |',
|
|
||||||
'| Customer List | `/kunde` | `/kunde` | entity |',
|
|
||||||
'| New Customer | `/kunde/neu` | `/kunde/neu` | entity, customergroup, number |',
|
|
||||||
'| Customer Detail | `/kunde/${entityId}` | `/kunde/312` | entity, customergroup |',
|
|
||||||
'| Customer Groups | `/kunde/gruppen` | `/kunde/gruppen` | customergroup |',
|
|
||||||
'| Article History | `/infos/articlehistory` | `/infos/articlehistory` | article (`GET /article/{sku}/history`) |',
|
|
||||||
'| Global History | `/infos/globalhistory` | `/infos/globalhistory` | company (`GET /company/history`) |',
|
|
||||||
'| Statistics | `/infos/statistics` | `/infos/statistics` | none; aggregate from order, delivery, ledger |',
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
const sections = [];
|
|
||||||
if (prefix) sections.push(prefix);
|
|
||||||
sections.push(basePrompt);
|
|
||||||
if (suffix) sections.push(suffix);
|
|
||||||
return sections.join('\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-conversation manager. Owns the SSE subscribers of one conversation and
|
* Per-conversation manager. Owns the SSE subscribers of one conversation and
|
||||||
@@ -391,6 +243,10 @@ export function historyToLlmMessages(history) {
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region ConversationManager
|
||||||
|
|
||||||
export class ConversationManager {
|
export class ConversationManager {
|
||||||
/**
|
/**
|
||||||
* @param {number} conversationId
|
* @param {number} conversationId
|
||||||
@@ -408,6 +264,8 @@ export class ConversationManager {
|
|||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #region Subscription & Broadcast
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast an SSE event to all subscribers of this conversation.
|
* Broadcast an SSE event to all subscribers of this conversation.
|
||||||
* @param {string} event
|
* @param {string} event
|
||||||
@@ -461,6 +319,10 @@ export class ConversationManager {
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Attachments
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves attachments sent with a user message to the company's VFS
|
* Saves attachments sent with a user message to the company's VFS
|
||||||
* under `attachments/<filename>` and registers them in `modelvfs`.
|
* under `attachments/<filename>` and registers them in `modelvfs`.
|
||||||
@@ -554,6 +416,10 @@ export class ConversationManager {
|
|||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Message Handlers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming user message.
|
* Handle an incoming user message.
|
||||||
* 1. Extracts text content and saves any attachments into the company VFS (`attachments/`).
|
* 1. Extracts text content and saves any attachments into the company VFS (`attachments/`).
|
||||||
@@ -649,7 +515,9 @@ export class ConversationManager {
|
|||||||
return toolMsg;
|
return toolMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region LLM Run
|
||||||
/**
|
/**
|
||||||
* Run the LLM over the stored transcript, stream the answer over SSE,
|
* Run the LLM over the stored transcript, stream the answer over SSE,
|
||||||
* persist reasoning, tool calls, and assistant messages, then return to `idle`.
|
* persist reasoning, tool calls, and assistant messages, then return to `idle`.
|
||||||
@@ -947,6 +815,10 @@ export class ConversationManager {
|
|||||||
this.broadcast('state', error ? { state: 'done', error } : { state: 'done' });
|
this.broadcast('state', error ? { state: 'done', error } : { state: 'done' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Teardown
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
this.orbiter?.abort('Conversation manager closed');
|
this.orbiter?.abort('Conversation manager closed');
|
||||||
@@ -958,8 +830,14 @@ export class ConversationManager {
|
|||||||
}
|
}
|
||||||
this.subscribers.clear();
|
this.subscribers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Manager Registry
|
||||||
|
|
||||||
/** @type {Map<number, ConversationManager>} */
|
/** @type {Map<number, ConversationManager>} */
|
||||||
const activeManagers = new Map();
|
const activeManagers = new Map();
|
||||||
|
|
||||||
@@ -1011,3 +889,5 @@ export function closeAllManagers() {
|
|||||||
activeManagers.delete(id);
|
activeManagers.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|||||||
1
src/services/prompts.js
Normal file
1
src/services/prompts.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '../tools/prompts.js';
|
||||||
@@ -7,6 +7,12 @@ import env from '../config/env.js';
|
|||||||
import { getImageByHash } from '../db/pic.js';
|
import { getImageByHash } from '../db/pic.js';
|
||||||
import { prepareVfs, extractFullHash } from './vfs.js';
|
import { prepareVfs, extractFullHash } from './vfs.js';
|
||||||
import { resolveOpenRouterKey, resolveDefaultModel } from './llm-key.js';
|
import { resolveOpenRouterKey, resolveDefaultModel } from './llm-key.js';
|
||||||
|
import {
|
||||||
|
TOOL_DESCRIPTIONS,
|
||||||
|
TOOL_PARAMETER_DESCRIPTIONS,
|
||||||
|
ANALYZE_IMAGE_VISION_COMPARE_PROMPT,
|
||||||
|
ANALYZE_IMAGE_VISION_DESCRIBE_PROMPT,
|
||||||
|
} from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* analyze_image — lets a vision model actually LOOK at images held in the VFS.
|
* analyze_image — lets a vision model actually LOOK at images held in the VFS.
|
||||||
@@ -275,9 +281,7 @@ async function fetchDatabaseImage(hash, googleAuthId) {
|
|||||||
export function createAnalyzeImageTool({ googleAuthId, model }) {
|
export function createAnalyzeImageTool({ googleAuthId, model }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'analyze_image',
|
name: 'analyze_image',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.analyze_image,
|
||||||
'Inspect 1-9 images via vision model. Multiple paths render as a labelled comparison grid. ' +
|
|
||||||
'Paths: VFS-relative ("images/foo.avif") or DB ("image/<hash>"). Returns model evaluation.',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -286,23 +290,23 @@ export function createAnalyzeImageTool({ googleAuthId, model }) {
|
|||||||
properties: {
|
properties: {
|
||||||
note: {
|
note: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'A note explaining what this image inspection is doing (or null). This note will be displayed to the user while the images are being analyzed.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.analyze_image.note,
|
||||||
},
|
},
|
||||||
paths: {
|
paths: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
description: '1–9 VFS paths or DB paths ("image/<hash>").',
|
description: TOOL_PARAMETER_DESCRIPTIONS.analyze_image.paths,
|
||||||
items: { type: 'string' },
|
items: { type: 'string' },
|
||||||
minItems: 1,
|
minItems: 1,
|
||||||
maxItems: MAX_IMAGES,
|
maxItems: MAX_IMAGES,
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'Inspection criteria or question, or null.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.analyze_image.prompt,
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
enum: [...Object.keys(GRID_CONFIGS), null],
|
enum: [...Object.keys(GRID_CONFIGS), null],
|
||||||
description: 'Grid layout or null for auto.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.analyze_image.grid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -378,14 +382,14 @@ export function createAnalyzeImageTool({ googleAuthId, model }) {
|
|||||||
// ── Ask the vision model ──────────────────────────────────────
|
// ── Ask the vision model ──────────────────────────────────────
|
||||||
const question = typeof prompt === 'string' && prompt.trim() ? prompt.trim() : null;
|
const question = typeof prompt === 'string' && prompt.trim() ? prompt.trim() : null;
|
||||||
const text = items.length > 1
|
const text = items.length > 1
|
||||||
? `${question || 'Compare these images, judge visual quality, flag watermarks, text overlays or defects, and say which one is best and why.'}\n\n` +
|
? `${question || ANALYZE_IMAGE_VISION_COMPARE_PROMPT}\n\n` +
|
||||||
`The grid is ${shape.name}. Each image has its file name printed beneath it:\n` +
|
`The grid is ${shape.name}. Each image has its file name printed beneath it:\n` +
|
||||||
items.map((it, idx) => {
|
items.map((it, idx) => {
|
||||||
const row = Math.floor(idx / GRID_CONFIGS[shape.name].cols) + 1;
|
const row = Math.floor(idx / GRID_CONFIGS[shape.name].cols) + 1;
|
||||||
const col = (idx % GRID_CONFIGS[shape.name].cols) + 1;
|
const col = (idx % GRID_CONFIGS[shape.name].cols) + 1;
|
||||||
return `- Row ${row}, Col ${col}: "${it.name}"`;
|
return `- Row ${row}, Col ${col}: "${it.name}"`;
|
||||||
}).join('\n')
|
}).join('\n')
|
||||||
: question || 'Describe this image, judge its visual quality, flag any watermark, text overlay or defect, and say whether it is usable as a product or category image.';
|
: question || ANALYZE_IMAGE_VISION_DESCRIBE_PROMPT;
|
||||||
|
|
||||||
const apiKey = await resolveOpenRouterKey(googleAuthId);
|
const apiKey = await resolveOpenRouterKey(googleAuthId);
|
||||||
const visionModel = (await resolveDefaultModel(googleAuthId)) || model;
|
const visionModel = (await resolveDefaultModel(googleAuthId)) || model;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineTool } from 'libllmorbiter';
|
import { defineTool } from 'libllmorbiter';
|
||||||
import * as db from '../db/company.js';
|
import * as db from '../db/company.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the apisuggestionsorissues tool allowing the agent to report
|
* Creates the apisuggestionsorissues tool allowing the agent to report
|
||||||
@@ -11,7 +12,7 @@ import * as db from '../db/company.js';
|
|||||||
export function createApiSuggestionsOrIssuesTool({ googleAuthId, companyId }) {
|
export function createApiSuggestionsOrIssuesTool({ googleAuthId, companyId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'apisuggestionsorissues',
|
name: 'apisuggestionsorissues',
|
||||||
description: 'Report missing API endpoint, feature request, bug, or issue for triage.',
|
description: TOOL_DESCRIPTIONS.apisuggestionsorissues,
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -20,16 +21,16 @@ export function createApiSuggestionsOrIssuesTool({ googleAuthId, companyId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
text: {
|
text: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Issue or request details.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.apisuggestionsorissues.text,
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
enum: ['missing_api', 'bug', 'suggestion', 'issue', 'other', null],
|
enum: ['missing_api', 'bug', 'suggestion', 'issue', 'other', null],
|
||||||
description: 'Report category or null.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.apisuggestionsorissues.type,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'Short summary or null.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.apisuggestionsorissues.title,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { defineTool, DirectReturnError } from 'libllmorbiter';
|
import { defineTool, DirectReturnError } from 'libllmorbiter';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ask_user tool:
|
* ask_user tool:
|
||||||
@@ -13,8 +14,7 @@ import { defineTool, DirectReturnError } from 'libllmorbiter';
|
|||||||
export function createAskUserTool() {
|
export function createAskUserTool() {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'ask_user',
|
name: 'ask_user',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.ask_user,
|
||||||
'Prompt user for clarification or decision with concise question and up to 3 options.',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -23,11 +23,11 @@ export function createAskUserTool() {
|
|||||||
properties: {
|
properties: {
|
||||||
question: {
|
question: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Question prompt.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.ask_user.question,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: ['array', 'null'],
|
type: ['array', 'null'],
|
||||||
description: 'Up to 3 option labels, or null.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.ask_user.options,
|
||||||
items: { type: 'string' },
|
items: { type: 'string' },
|
||||||
maxItems: 3,
|
maxItems: 3,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { defineTool } from 'libllmorbiter';
|
import { defineTool } from 'libllmorbiter';
|
||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const MAX_RESPONSE_CHARS = 20_000;
|
const MAX_RESPONSE_CHARS = 20_000;
|
||||||
const REQUEST_TIMEOUT_MS = 30_000;
|
const REQUEST_TIMEOUT_MS = 30_000;
|
||||||
@@ -14,7 +15,7 @@ const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|||||||
export function createDashbackRequestTool({ googleAuthId }) {
|
export function createDashbackRequestTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'dashback_request',
|
name: 'dashback_request',
|
||||||
description: 'Call Dashback REST API. Paths are root-relative (e.g. "/article/2").',
|
description: TOOL_DESCRIPTIONS.dashback_request,
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -23,12 +24,12 @@ export function createDashbackRequestTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
note: {
|
note: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'A note explaining what this request is doing (or null). This note will be displayed to the user while the request is executing.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.dashback_request.note,
|
||||||
},
|
},
|
||||||
method: { type: 'string', enum: METHODS, description: 'HTTP method.' },
|
method: { type: 'string', enum: METHODS, description: TOOL_PARAMETER_DESCRIPTIONS.dashback_request.method },
|
||||||
path: { type: 'string', description: 'Endpoint path (e.g. "/article/2").' },
|
path: { type: 'string', description: TOOL_PARAMETER_DESCRIPTIONS.dashback_request.path },
|
||||||
query: { type: ['object', 'null'], description: 'Query parameters object or null.' },
|
query: { type: ['object', 'null'], description: TOOL_PARAMETER_DESCRIPTIONS.dashback_request.query },
|
||||||
body: { type: ['string', 'null'], description: 'JSON request payload string or null.' },
|
body: { type: ['string', 'null'], description: TOOL_PARAMETER_DESCRIPTIONS.dashback_request.body },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
invoke: async ({ method = 'GET', path, query = null, body = null, note }) => {
|
invoke: async ({ method = 'GET', path, query = null, body = null, note }) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the standard GS1 check digit for a string of numeric digits.
|
* Calculates the standard GS1 check digit for a string of numeric digits.
|
||||||
@@ -44,9 +45,7 @@ function resolveBarcodeFormat(length) {
|
|||||||
export function createEanChecksumTool() {
|
export function createEanChecksumTool() {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'ean_checksum',
|
name: 'ean_checksum',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.ean_checksum,
|
||||||
'Validate EAN/GTIN/UPC barcode check digits or calculate the missing check digit for barcodes ' +
|
|
||||||
'(EAN-13, EAN-8, UPC-A, GTIN-14). Strips spaces and hyphens automatically.',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -55,16 +54,16 @@ export function createEanChecksumTool() {
|
|||||||
properties: {
|
properties: {
|
||||||
note: {
|
note: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'A note explaining what this EAN checksum check is doing (or null). This note will be displayed to the user while executing.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.ean_checksum.note,
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Barcode number to validate or complete (e.g. 12 digits for EAN-13 check digit calculation, or 13 digits for EAN-13 validation).',
|
description: TOOL_PARAMETER_DESCRIPTIONS.ean_checksum.code,
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
enum: ['auto', 'validate', 'calculate', null],
|
enum: ['auto', 'validate', 'calculate', null],
|
||||||
description: 'Optional action: "auto" (default: infer from length), "validate" (check last digit), "calculate" (compute check digit for the given payload), or null.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.ean_checksum.action,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import env from '../config/env.js';
|
|||||||
import { getImageByHash } from '../db/pic.js';
|
import { getImageByHash } from '../db/pic.js';
|
||||||
import { prepareVfs, syncVfsToDb, extractFullHash, isReadOnlyDatabasePath } from './vfs.js';
|
import { prepareVfs, syncVfsToDb, extractFullHash, isReadOnlyDatabasePath } from './vfs.js';
|
||||||
import { resolveOpenRouterKey, resolveImageModel } from './llm-key.js';
|
import { resolveOpenRouterKey, resolveImageModel } from './llm-key.js';
|
||||||
|
import { TOOL_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* edit-image — generate or edit images using the modelslugimage company setting.
|
* edit-image — generate or edit images using the modelslugimage company setting.
|
||||||
@@ -251,10 +252,7 @@ async function processAttachmentToDataUrl(rawBuffer, high) {
|
|||||||
export function createEditImageTool({ googleAuthId, model: fallbackModel }) {
|
export function createEditImageTool({ googleAuthId, model: fallbackModel }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'edit_image',
|
name: 'edit_image',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.edit_image,
|
||||||
'Generate or edit images using the configured company image model (modelslugimage). ' +
|
|
||||||
'Supports reference image attachments fed in low or high quality, output size tier, ' +
|
|
||||||
'aspect ratio, and saving directly to a specified VFS output path (always saved as AVIF).',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from 'path';
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import { isSkillPath, listSkillFiles, SKILL_EXT } from './skill.js';
|
import { isSkillPath, listSkillFiles, SKILL_EXT } from './skill.js';
|
||||||
import { prepareVfs } from './vfs.js';
|
import { prepareVfs } from './vfs.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* find_files — lists the contents of the virtual filesystem (VFS).
|
* find_files — lists the contents of the virtual filesystem (VFS).
|
||||||
@@ -80,9 +81,7 @@ function walkVfs(absDir, vfsRoot, recursive, depth, maxDepth) {
|
|||||||
export function createFindFilesTool({ googleAuthId }) {
|
export function createFindFilesTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'find_files',
|
name: 'find_files',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.find_files,
|
||||||
'List VFS files. Root-relative forward-slash paths. ' +
|
|
||||||
'Set skills=true to list root-level "<slug>.SKILL" files (reusable instructions exempt from reset_vfs; read via read_file).',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -90,27 +89,27 @@ export function createFindFilesTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
dirname: {
|
dirname: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'VFS directory to list or null. Default "." (root). Must be "." or empty if skills=true.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.find_files.dirname,
|
||||||
},
|
},
|
||||||
recursive: {
|
recursive: {
|
||||||
type: ['boolean', 'null'],
|
type: ['boolean', 'null'],
|
||||||
description: 'Walk subdirectories or null. Default false.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.find_files.recursive,
|
||||||
},
|
},
|
||||||
skills: {
|
skills: {
|
||||||
type: ['boolean', 'null'],
|
type: ['boolean', 'null'],
|
||||||
description: 'List only root SKILL files with parsed titles or null. Overrides dirname/recursive. Default false.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.find_files.skills,
|
||||||
},
|
},
|
||||||
seek: {
|
seek: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: 'Pagination offset or null. Default 0.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.find_files.seek,
|
||||||
},
|
},
|
||||||
limit: {
|
limit: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: `Max entries to return or null. Default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}.`,
|
description: TOOL_PARAMETER_DESCRIPTIONS.find_files.limit,
|
||||||
},
|
},
|
||||||
maxDepth: {
|
maxDepth: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: 'Max recursive directory depth or null. Default 3.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.find_files.maxDepth,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from 'path';
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
import { createVfs, prepareVfs, syncVfsToDb } from './vfs.js';
|
import { createVfs, prepareVfs, syncVfsToDb } from './vfs.js';
|
||||||
import { runSandboxJob } from '../services/javascriptSandbox.js';
|
import { runSandboxJob } from '../services/javascriptSandbox.js';
|
||||||
|
|
||||||
@@ -271,17 +272,7 @@ function formatSandboxError(code, report) {
|
|||||||
export function createJavascriptExecuteTool({ googleAuthId }) {
|
export function createJavascriptExecuteTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'javascript_execute',
|
name: 'javascript_execute',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.javascript_execute,
|
||||||
'Execute JS in an isolated worker sandbox using either inline `code` or a VFS applet. Provide exactly one of `code` and `appletvfsfile`. ' +
|
|
||||||
'Applets are the preferred method when the first execution may throw: save a `.applet.js` file in the VFS so it can be inspected and further edited with `patch`, then call it by VFS path. ' +
|
|
||||||
'Applet files must start with `// @applet-input-schema {JSON Schema}` on line 1 and `// @applet-output-schema <columns>` on line 2. ' +
|
|
||||||
'The input schema describes the GUI form passed as `applet_input`. The output line declares the RESULT COLUMNS as typed CSV: `name:type` pairs separated by `;` (e.g. `id:integer;name:string;active:boolean`); a bare `name` defaults to string, and the allowed types are string, integer, number, boolean and date. ' +
|
|
||||||
'Applets return typed CSV, NOT JSON: export a function that receives `applet_input` and returns a row object, or an array of row objects, whose keys are the declared columns (e.g. `return [{ id: 1, name: "Kaffee" }]`). Only declared columns are emitted, in header order; missing keys become empty cells. The tool result then carries `result` as a `;`-separated CSV string with a header row, plus `columns` (name/type) and `rows` (data row count). Returning a single object yields one data row. ' +
|
|
||||||
'Inline code: assign `module.exports` to return value (sync value or Promise; wrap async in IIFE: `module.exports = (async () => { ... })()`). ' +
|
|
||||||
'REST API: `await fetch("/path")` (authenticated). ' +
|
|
||||||
'VFS: directly use global `fs.readFileSync`/`writeFileSync` for binary artifacts/buffers/images (`image/` is read-only; paths must be relative paths, root is not allowed). VFS can host a static website (HTML, CSS, JS, images, etc.). ' +
|
|
||||||
'Embed generated images/links in markdown: ``. Internet links use `https://...` or `hostname.com`/`hostname.com/filename`; a pure relative file with no dotted hostname is always a VFS relative path. Use find_files/read_file/patch for text. ' +
|
|
||||||
'All APIs are pre-injected globals: `fs`/`vfs` (virtual filesystem), `sharp` (image processing), `ExcelJS`/`exceljs` (spreadsheet processing with VFS support for readFile/writeFile/writeBuffer/load), `PDFDocument`/`PDFKit`/`pdfkit` (PDF generation with VFS support for toFile/toBuffer/pipe), WebCrypto `crypto` (`subtle`, `randomUUID`), and standard web globals. Do NOT use `require()` or `import` (disabled; all necessary APIs are already in global scope).',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -289,28 +280,28 @@ export function createJavascriptExecuteTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
note: {
|
note: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'A note explaining what this code is doing (or null). This note will be displayed to the user while the code is executing.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.javascript_execute.note,
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Optional inline JavaScript code. Provide exactly one of this and appletvfsfile. Assign result to `module.exports`. Async code must use IIFE. Do not use require() or import.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.javascript_execute.code,
|
||||||
},
|
},
|
||||||
appletvfsfile: {
|
appletvfsfile: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Optional VFS-relative path to a .applet.js file. Provide exactly one of this and code. The file must begin with the two applet metadata lines (`@applet-input-schema` JSON Schema, then `@applet-output-schema` typed CSV columns like `id:integer;name:string`).',
|
description: TOOL_PARAMETER_DESCRIPTIONS.javascript_execute.appletvfsfile,
|
||||||
},
|
},
|
||||||
applet_input: {
|
applet_input: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description: 'Input values passed to the exported function when running appletvfsfile. Omit to pass an empty object. The applet returns typed CSV rows mapped onto its `@applet-output-schema` columns.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.javascript_execute.applet_input,
|
||||||
additionalProperties: true,
|
additionalProperties: true,
|
||||||
},
|
},
|
||||||
timeout: {
|
timeout: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: `Execution timeout in ms or null. Default ${DEFAULT_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS}.`,
|
description: TOOL_PARAMETER_DESCRIPTIONS.javascript_execute.timeout,
|
||||||
},
|
},
|
||||||
max_output: {
|
max_output: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: `Max stdout chars or null. Default ${DEFAULT_MAX_OUTPUT}.`,
|
description: TOOL_PARAMETER_DESCRIPTIONS.javascript_execute.max_output,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
|
import { TOOL_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const DEFAULT_TAX_RATE = 19;
|
const DEFAULT_TAX_RATE = 19;
|
||||||
const DEFAULT_PRECISION = 6;
|
const DEFAULT_PRECISION = 6;
|
||||||
@@ -109,9 +110,7 @@ export function calculatePrice({ net, gross, amount, from, taxRate = DEFAULT_TAX
|
|||||||
export function createNetGrossCalcTool() {
|
export function createNetGrossCalcTool() {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'net_gross_calc',
|
name: 'net_gross_calc',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.net_gross_calc,
|
||||||
'Convert between net and gross prices (net to gross, gross to net) with VAT/tax rate (default 19%). ' +
|
|
||||||
'Computes 2-decimal rounded display values and 6-decimal high-precision net values for API article creation.',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { defineTool, ToolError } from 'libllmorbiter';
|
|||||||
import { isReadOnlyDatabasePath } from './vfs.js';
|
import { isReadOnlyDatabasePath } from './vfs.js';
|
||||||
import { isSkillPath, renderSkill, parseSkill, SKILL_EXT, skillFileName } from './skill.js';
|
import { isSkillPath, renderSkill, parseSkill, SKILL_EXT, skillFileName } from './skill.js';
|
||||||
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, PATCH_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* patch — applies edit operations to files in the virtual filesystem (VFS).
|
* patch — applies edit operations to files in the virtual filesystem (VFS).
|
||||||
@@ -66,11 +67,7 @@ function toLines(text) {
|
|||||||
export function createPatchTool({ googleAuthId }) {
|
export function createPatchTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'patch',
|
name: 'patch',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.patch,
|
||||||
'Create, overwrite, or search-and-replace text in VFS files. Operations apply in order. ' +
|
|
||||||
'Mode: search null/omitted = create or overwrite (directories auto-created). ' +
|
|
||||||
'search provided = replace first verbatim occurrence (read file with read_file first). ' +
|
|
||||||
'SKILL files: root-only "<slug>.SKILL" (line 1 is title, or provide title field).',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -79,7 +76,7 @@ export function createPatchTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
operations: {
|
operations: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
description: 'Ordered patch operations.',
|
description: PATCH_PARAMETER_DESCRIPTIONS.operations,
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
@@ -87,23 +84,23 @@ export function createPatchTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
file: {
|
file: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'VFS path (e.g. "notes.md", "pipeline.SKILL"). SKILL files must be root-level (no "/").',
|
description: PATCH_PARAMETER_DESCRIPTIONS.file,
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'Exact text to replace. null/omitted = overwrite or create file.',
|
description: PATCH_PARAMETER_DESCRIPTIONS.search,
|
||||||
},
|
},
|
||||||
replace: {
|
replace: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Replacement text.',
|
description: PATCH_PARAMETER_DESCRIPTIONS.replace,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'Optional line-1 title for SKILL files or null.',
|
description: PATCH_PARAMETER_DESCRIPTIONS.title,
|
||||||
},
|
},
|
||||||
skiplines: {
|
skiplines: {
|
||||||
type: ['integer', 'null'],
|
type: ['integer', 'null'],
|
||||||
description: 'Lines to skip before searching or null.',
|
description: PATCH_PARAMETER_DESCRIPTIONS.skiplines,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from 'path';
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import { pdfToText, splitPdfPages, pdfToPng } from 'pdfconvert-wasm';
|
import { pdfToText, splitPdfPages, pdfToPng } from 'pdfconvert-wasm';
|
||||||
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const IMAGE_FOLDER = 'images';
|
const IMAGE_FOLDER = 'images';
|
||||||
const DEFAULT_DPI = 150;
|
const DEFAULT_DPI = 150;
|
||||||
@@ -42,8 +43,7 @@ function slugifyBase(filename) {
|
|||||||
export function createPdfReadTool({ googleAuthId }) {
|
export function createPdfReadTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'pdf_read',
|
name: 'pdf_read',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.pdf_read,
|
||||||
'Read and convert PDF/text documents to text or VFS PNG images (stored in "images/").',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -52,20 +52,20 @@ export function createPdfReadTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
path: {
|
path: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'VFS path or HTTP(S) URL.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.pdf_read.path,
|
||||||
},
|
},
|
||||||
format: {
|
format: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
enum: ['txt', 'png', 'both', null],
|
enum: ['txt', 'png', 'both', null],
|
||||||
description: 'Output format: "txt" (layout text), "png" (page images), "both", or null. Default "txt".',
|
description: TOOL_PARAMETER_DESCRIPTIONS.pdf_read.format,
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: '1-based page number or null for all pages.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.pdf_read.page,
|
||||||
},
|
},
|
||||||
dpi: {
|
dpi: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: 'PNG render DPI or null. Default 150.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.pdf_read.dpi,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
436
src/tools/prompts.js
Normal file
436
src/tools/prompts.js
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
import * as dbCompanySetting from '../db/companysetting.js';
|
||||||
|
import { listSkillFiles } from './skill.js';
|
||||||
|
|
||||||
|
// #region Tool Prompts & Descriptions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptions for all tools registered in the agent's toolbelt.
|
||||||
|
* Used in defineTool({ description }) across src/tools/*.
|
||||||
|
*/
|
||||||
|
export const TOOL_DESCRIPTIONS = {
|
||||||
|
dashback_request:
|
||||||
|
'Call Dashback REST API. Paths are root-relative (e.g. "/article/2").',
|
||||||
|
|
||||||
|
ask_user:
|
||||||
|
'Prompt user for clarification or decision with concise question and up to 3 options.',
|
||||||
|
|
||||||
|
javascript_execute:
|
||||||
|
'Execute JS in an isolated worker sandbox using either inline `code` or a VFS applet. Provide exactly one of `code` and `appletvfsfile`. ' +
|
||||||
|
'Applets are the preferred method when the first execution may throw: save a `.applet.js` file in the VFS so it can be inspected and further edited with `patch`, then call it by VFS path. ' +
|
||||||
|
'Applet files must start with `// @applet-input-schema {JSON Schema}` on line 1 and `// @applet-output-schema <columns>` on line 2. ' +
|
||||||
|
'The input schema describes the GUI form passed as `applet_input`. The output line declares the RESULT COLUMNS as typed CSV: `name:type` pairs separated by `;` (e.g. `id:integer;name:string;active:boolean`); a bare `name` defaults to string, and the allowed types are string, integer, number, boolean and date. ' +
|
||||||
|
'Applets return typed CSV, NOT JSON: export a function that receives `applet_input` and returns a row object, or an array of row objects, whose keys are the declared columns (e.g. `return [{ id: 1, name: "Kaffee" }]`). Only declared columns are emitted, in header order; missing keys become empty cells. The tool result then carries `result` as a `;`-separated CSV string with a header row, plus `columns` (name/type) and `rows` (data row count). Returning a single object yields one data row. ' +
|
||||||
|
'Inline code: assign `module.exports` to return value (sync value or Promise; wrap async in IIFE: `module.exports = (async () => { ... })()`). ' +
|
||||||
|
'REST API: `await fetch("/path")` (authenticated). ' +
|
||||||
|
'VFS: directly use global `fs.readFileSync`/`writeFileSync` for binary artifacts/buffers/images (`image/` is read-only; paths must be relative paths, root is not allowed). VFS can host a static website (HTML, CSS, JS, images, etc.). ' +
|
||||||
|
'Embed generated images/links in markdown: ``. Internet links use `https://...` or `hostname.com`/`hostname.com/filename`; a pure relative file with no dotted hostname is always a VFS relative path. Use find_files/read_file/patch for text. ' +
|
||||||
|
'All APIs are pre-injected globals: `fs`/`vfs` (virtual filesystem), `sharp` (image processing), `ExcelJS`/`exceljs` (spreadsheet processing with VFS support for readFile/writeFile/writeBuffer/load), `PDFDocument`/`PDFKit`/`pdfkit` (PDF generation with VFS support for toFile/toBuffer/pipe), WebCrypto `crypto` (`subtle`, `randomUUID`), and standard web globals. Do NOT use `require()` or `import` (disabled; all necessary APIs are already in global scope).',
|
||||||
|
|
||||||
|
find_files:
|
||||||
|
'List VFS files. Root-relative forward-slash paths. ' +
|
||||||
|
'Set skills=true to list root-level "<slug>.SKILL" files (reusable instructions exempt from reset_vfs; read via read_file).',
|
||||||
|
|
||||||
|
read_file:
|
||||||
|
'Read text file from VFS. Root-relative forward-slash paths. ' +
|
||||||
|
'SKILL files return parsed title and markdown content. ' +
|
||||||
|
'Windowed via seek/limit (0-indexed lines). Binary files not supported (use analyze_image for images).',
|
||||||
|
|
||||||
|
patch:
|
||||||
|
'Create, overwrite, or search-and-replace text in VFS files. Operations apply in order. ' +
|
||||||
|
'Mode: search null/omitted = create or overwrite (directories auto-created). ' +
|
||||||
|
'search provided = replace first verbatim occurrence (read file with read_file first). ' +
|
||||||
|
'SKILL files: root-only "<slug>.SKILL" (line 1 is title, or provide title field).',
|
||||||
|
|
||||||
|
search_images:
|
||||||
|
'Search Google Images and download candidates to VFS as AVIF. Returns VFS paths for inspection via analyze_image.',
|
||||||
|
|
||||||
|
analyze_image:
|
||||||
|
'Inspect 1-9 images via vision model. Multiple paths render as a labelled comparison grid. ' +
|
||||||
|
'Paths: VFS-relative ("images/foo.avif") or DB ("image/<hash>"). Returns model evaluation.',
|
||||||
|
|
||||||
|
web_search:
|
||||||
|
'Search Google via Serper.dev. Use timePeriod "day" or "week" for news/recent events. Returns 10 results per page.',
|
||||||
|
|
||||||
|
url_get:
|
||||||
|
'Fetch and scrape web page content via Serper Scrape (text, markdown, metadata).',
|
||||||
|
|
||||||
|
pdf_read:
|
||||||
|
'Read and convert PDF/text documents to text or VFS PNG images (stored in "images/").',
|
||||||
|
|
||||||
|
reset_vfs:
|
||||||
|
'Clear VFS files, images, and artifacts. Preserves SKILL files by default.',
|
||||||
|
|
||||||
|
rm:
|
||||||
|
'Delete single VFS file (e.g. specific artifact or SKILL file). Irreversible.',
|
||||||
|
|
||||||
|
apisuggestionsorissues:
|
||||||
|
'Report missing API endpoint, feature request, bug, or issue for triage.',
|
||||||
|
|
||||||
|
search_articles:
|
||||||
|
'Text search articles by name, brand, EAN, SKU, or supplier SKU. ' +
|
||||||
|
'Returns window of full articles (default 3, max 20), offset, and total count. Ordered alphabetically by name.',
|
||||||
|
|
||||||
|
edit_image:
|
||||||
|
'Generate or edit images using the configured company image model (modelslugimage). ' +
|
||||||
|
'Supports reference image attachments fed in low or high quality, output size tier, ' +
|
||||||
|
'aspect ratio, and saving directly to a specified VFS output path (always saved as AVIF).',
|
||||||
|
|
||||||
|
ean_checksum:
|
||||||
|
'Validate EAN/GTIN/UPC barcode check digits or calculate the missing check digit for barcodes ' +
|
||||||
|
'(EAN-13, EAN-8, UPC-A, GTIN-14). Strips spaces and hyphens automatically.',
|
||||||
|
|
||||||
|
net_gross_calc:
|
||||||
|
'Convert between net and gross prices (net to gross, gross to net) with VAT/tax rate (default 19%). ' +
|
||||||
|
'Computes 2-decimal rounded display values and 6-decimal high-precision net values for API article creation.',
|
||||||
|
|
||||||
|
set_user_route:
|
||||||
|
'Navigate the user to a frontend route. Use this when the user says "show me the article" or asks to see, open, or view a specific page or entity (e.g. an article detail page, orders, quotes, deliveries, or customer). Refer to the User Perspective Route Construction Matrix for valid route patterns.',
|
||||||
|
};
|
||||||
|
|
||||||
|
// #region Tool Parameter Descriptions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptions for tool input parameters across src/tools/*.
|
||||||
|
*/
|
||||||
|
export const TOOL_PARAMETER_DESCRIPTIONS = {
|
||||||
|
dashback_request: {
|
||||||
|
note: 'A note explaining what this request is doing (or null). This note will be displayed to the user while the request is executing.',
|
||||||
|
method: 'HTTP method.',
|
||||||
|
path: 'Endpoint path (e.g. "/article/2").',
|
||||||
|
query: 'Query parameters object or null.',
|
||||||
|
body: 'JSON request payload string or null.',
|
||||||
|
},
|
||||||
|
|
||||||
|
ask_user: {
|
||||||
|
question: 'Question prompt.',
|
||||||
|
options: 'Up to 3 option labels, or null.',
|
||||||
|
},
|
||||||
|
|
||||||
|
javascript_execute: {
|
||||||
|
note: 'A note explaining what this code is doing (or null). This note will be displayed to the user while the code is executing.',
|
||||||
|
code: 'Optional inline JavaScript code. Provide exactly one of this and appletvfsfile. Assign result to `module.exports`. Async code must use IIFE. Do not use require() or import.',
|
||||||
|
appletvfsfile:
|
||||||
|
'Optional VFS-relative path to a .applet.js file. Provide exactly one of this and code. The file must begin with the two applet metadata lines (`@applet-input-schema` JSON Schema, then `@applet-output-schema` typed CSV columns like `id:integer;name:string`).',
|
||||||
|
applet_input:
|
||||||
|
'Input values passed to the exported function when running appletvfsfile. Omit to pass an empty object. The applet returns typed CSV rows mapped onto its `@applet-output-schema` columns.',
|
||||||
|
timeout: 'Execution timeout in ms or null. Default 20000, max 60000.',
|
||||||
|
max_output: 'Max stdout chars or null. Default 20000.',
|
||||||
|
},
|
||||||
|
|
||||||
|
find_files: {
|
||||||
|
dirname: 'VFS directory to list or null. Default "." (root). Must be "." or empty if skills=true.',
|
||||||
|
recursive: 'Walk subdirectories or null. Default false.',
|
||||||
|
skills: 'List only root SKILL files with parsed titles or null. Overrides dirname/recursive. Default false.',
|
||||||
|
seek: 'Pagination offset or null. Default 0.',
|
||||||
|
limit: 'Max entries to return or null. Default 50, max 500.',
|
||||||
|
maxDepth: 'Max recursive directory depth or null. Default 3.',
|
||||||
|
},
|
||||||
|
|
||||||
|
read_file: {
|
||||||
|
filename: 'VFS file path.',
|
||||||
|
seek: 'Line offset (0-indexed) or null. Default 0.',
|
||||||
|
limit: 'Max lines to return or null. Default 400, max 5000.',
|
||||||
|
},
|
||||||
|
|
||||||
|
patch: {
|
||||||
|
operations: 'Ordered patch operations.',
|
||||||
|
file: 'VFS path (e.g. "notes.md", "pipeline.SKILL"). SKILL files must be root-level (no "/").',
|
||||||
|
search: 'Exact text to replace. null/omitted = overwrite or create file.',
|
||||||
|
replace: 'Replacement text.',
|
||||||
|
title: 'Optional line-1 title for SKILL files or null.',
|
||||||
|
skiplines: 'Lines to skip before searching or null.',
|
||||||
|
},
|
||||||
|
|
||||||
|
search_images: {
|
||||||
|
note: 'A note explaining what this image search is doing (or null). This note will be displayed to the user while executing.',
|
||||||
|
query: 'Search query.',
|
||||||
|
count: 'Candidate count or null. Default 4, max 9.',
|
||||||
|
gl: 'Country code or null. Default "de".',
|
||||||
|
hl: 'Language code or null. Default "de".',
|
||||||
|
},
|
||||||
|
|
||||||
|
analyze_image: {
|
||||||
|
note: 'A note explaining what this image inspection is doing (or null). This note will be displayed to the user while the images are being analyzed.',
|
||||||
|
paths: '1–9 VFS paths or DB paths ("image/<hash>").',
|
||||||
|
prompt: 'Inspection criteria or question, or null.',
|
||||||
|
grid: 'Grid layout or null for auto.',
|
||||||
|
},
|
||||||
|
|
||||||
|
web_search: {
|
||||||
|
note: 'A note explaining what this search is doing (or null). This note will be displayed to the user while the search is executing.',
|
||||||
|
query: 'Search query.',
|
||||||
|
timePeriod: 'Time filter: "day", "week", "month", or "year", or null.',
|
||||||
|
gl: 'Country code or null. Default "de".',
|
||||||
|
hl: 'Language code or null. Default "de".',
|
||||||
|
page: 'Result page number (10/page) or null. Default 1.',
|
||||||
|
},
|
||||||
|
|
||||||
|
url_get: {
|
||||||
|
url: 'Target URL.',
|
||||||
|
},
|
||||||
|
|
||||||
|
pdf_read: {
|
||||||
|
path: 'VFS path or HTTP(S) URL.',
|
||||||
|
format: 'Output format: "txt" (layout text), "png" (page images), "both", or null. Default "txt".',
|
||||||
|
page: '1-based page number or null for all pages.',
|
||||||
|
dpi: 'PNG render DPI or null. Default 150.',
|
||||||
|
},
|
||||||
|
|
||||||
|
reset_vfs: {
|
||||||
|
include_skills: 'Delete SKILL files too (or null). Default false.',
|
||||||
|
},
|
||||||
|
|
||||||
|
rm: {
|
||||||
|
filename: 'VFS file path to delete.',
|
||||||
|
},
|
||||||
|
|
||||||
|
apisuggestionsorissues: {
|
||||||
|
text: 'Issue or request details.',
|
||||||
|
type: 'Report category or null.',
|
||||||
|
title: 'Short summary or null.',
|
||||||
|
},
|
||||||
|
|
||||||
|
search_articles: {
|
||||||
|
searchquery:
|
||||||
|
'Words that must all match across name, brand, EAN, SKU, or supplier SKU (AND logic, order-independent) or null.',
|
||||||
|
excludewords:
|
||||||
|
'Words that if all match exclude the article (AND logic, order-independent) or null.',
|
||||||
|
offset: 'Pagination offset or null. Default 0.',
|
||||||
|
limit: 'Result window size (1–20) or null. Default 3.',
|
||||||
|
},
|
||||||
|
|
||||||
|
edit_image: {
|
||||||
|
note: 'A note explaining what this image generation or edit is doing (or null). This note will be displayed to the user while the image is being generated or edited.',
|
||||||
|
prompt: 'Description of the image to generate, or edit/transformation instructions when attachments are provided.',
|
||||||
|
attachments:
|
||||||
|
'Optional reference image(s) for editing/image-to-image or null. Omit for text-to-image generation.',
|
||||||
|
attachment_vfsfilename:
|
||||||
|
'VFS path (e.g. "images/sample.png") or database path ("image/<hash>").',
|
||||||
|
attachment_high:
|
||||||
|
'Feed image in high quality/resolution if true; low quality/resolution if false or null (default).',
|
||||||
|
size: 'Resolution tier (3 choices): "1K" (standard, default), "2K" (high quality), "4K" (ultra high), or null.',
|
||||||
|
ratio: 'Aspect ratio (3 choices): "1:1" (square, default), "4:3" (landscape), "3:4" (portrait), or null.',
|
||||||
|
vfsoutputfilename:
|
||||||
|
'VFS path where the output image should be saved (e.g. "images/edited.avif") or null. Defaults to "images/generated-<timestamp>.avif". Output is always converted to AVIF.',
|
||||||
|
},
|
||||||
|
|
||||||
|
ean_checksum: {
|
||||||
|
note: 'A note explaining what this EAN checksum check is doing (or null). This note will be displayed to the user while executing.',
|
||||||
|
code: 'Barcode number to validate or complete (e.g. 12 digits for EAN-13 check digit calculation, or 13 digits for EAN-13 validation).',
|
||||||
|
action: 'Optional action: "auto" (default: infer from length), "validate" (check last digit), "calculate" (compute check digit for the given payload), or null.',
|
||||||
|
},
|
||||||
|
|
||||||
|
net_gross_calc: {
|
||||||
|
note: 'A note explaining what this price calculation is doing. This note will be displayed to the user while executing.',
|
||||||
|
gross: 'Gross price (incl. VAT) or null. When provided, computes net price and tax amount.',
|
||||||
|
net: 'Net price (excl. VAT) or null. When provided, computes gross price and tax amount.',
|
||||||
|
taxRate: 'Tax/VAT rate percentage (e.g. 19 for 19%, 7 for 7%) or null. Default 19.',
|
||||||
|
amount: 'Generic price amount or null when used with "from" ("gross" or "net").',
|
||||||
|
from: 'Direction when using "amount": "gross" (default) or "net".',
|
||||||
|
precision: 'Decimal precision for net price API value or null (default 4, between 2 and 8).',
|
||||||
|
items: 'Optional array of prices or objects for batch calculation: [19.99, 49.95] or [{ gross: 19.99, taxRate: 7 }].',
|
||||||
|
},
|
||||||
|
|
||||||
|
set_user_route: {
|
||||||
|
route: 'Target frontend route path according to the Route Construction Matrix (e.g. "/artikel/ART-10042", "/kategorien/elektronik/kabel", "/auftrag/1001").',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PATCH_PARAMETER_DESCRIPTIONS = TOOL_PARAMETER_DESCRIPTIONS.patch;
|
||||||
|
export const PATCH_PARAM_DESCRIPTIONS = TOOL_PARAMETER_DESCRIPTIONS.patch;
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Tool-specific Subprompts
|
||||||
|
export const ANALYZE_IMAGE_VISION_COMPARE_PROMPT =
|
||||||
|
'Compare these images, judge visual quality, flag watermarks, text overlays or defects, and say which one is best and why.';
|
||||||
|
|
||||||
|
export const ANALYZE_IMAGE_VISION_DESCRIBE_PROMPT =
|
||||||
|
'Describe this image, judge its visual quality, flag any watermark, text overlay or defect, and say whether it is usable as a product or category image.';
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region System Prompt Components
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concise tool listing included directly in the system prompt.
|
||||||
|
*/
|
||||||
|
export const SYSTEM_PROMPT_TOOLS = [
|
||||||
|
'TOOLS:',
|
||||||
|
'1. dashback_request: HTTP requests (GET, POST, PUT, PATCH, DELETE) to QuixPOS API.',
|
||||||
|
'2. ask_user: Prompt user for missing decisions or parameters; pauses until answered.',
|
||||||
|
'3. javascript_execute: Sandboxed Node env for computation, binary I/O, aggregation. Assign module.exports. Has fetch("/path"), vfs, sharp, ExcelJS.',
|
||||||
|
'4. find_files: List VFS files (skills=true for SKILL files).',
|
||||||
|
'5. read_file: Read VFS text/CSV/JSON files (seek/limit windowed).',
|
||||||
|
'6. patch: Create/overwrite/replace text in VFS files, incl. SKILL files (only text write path).',
|
||||||
|
'7. search_images: Search Google Images; downloads AVIF candidates to VFS.',
|
||||||
|
'8. analyze_image: Vision analysis of up to 9 images (VFS paths or "image/<hash>"). Always evaluate images with this tool.',
|
||||||
|
'9. web_search: Google web search (timePeriod: "day"|"week" for current events).',
|
||||||
|
'10. url_get: Fetch/scrape web page as markdown.',
|
||||||
|
'11. pdf_read: Convert PDF to layout text ("txt") or VFS page images ("png").',
|
||||||
|
'12. reset_vfs: Delete all non-SKILL VFS files.',
|
||||||
|
'13. rm: Delete a single VFS file (including SKILL files).',
|
||||||
|
'14. apisuggestionsorissues: Report API bugs or missing endpoints.',
|
||||||
|
'15. search_articles: Search by name, brand, EAN, SKU, supplier SKU. Returns full articles window (default 3, max 20), offset, total.',
|
||||||
|
'16. edit_image: Generate/edit images with the company image model (modelslugimage). Supports attachments (low/high quality), sizes 1K/2K/4K, ratios 1:1/4:3/3:4. Output: AVIF in VFS.',
|
||||||
|
'17. ean_checksum: Validate or generate barcode check digits (EAN-13, EAN-8, UPC-A, GTIN-14).',
|
||||||
|
'18. net_gross_calc: Convert gross/net with VAT (default 19%). Returns 2-decimal display values and high-precision net for the API.',
|
||||||
|
'19. set-user-route: Navigate the UI (e.g. "show me the article", "open quote 1001") using the Route Matrix.',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
export const SYSTEM_PROMPT_VFS_RULES = [
|
||||||
|
'VFS & SKILLS:',
|
||||||
|
'- Persistent per-company VFS; root-relative forward-slash paths (e.g. "labels/sheet1.png"). Can host a static website.',
|
||||||
|
'- User uploads go to "attachments/…", tagged in messages as `[ATTACHMENT name= path= type=]`. Inspect with read_file (text/csv/json), pdf_read (PDF), analyze_image (images), javascript_execute + ExcelJS (spreadsheets).',
|
||||||
|
'- Markdown links: internet links are "https://…" or dotted hostnames ("hostname.com/file"); anything else is a VFS path (`[download](report.csv)`).',
|
||||||
|
'- SKILL files: root-only "<slug>.SKILL" markdown (first line = title). Discover via find_files({ skills: true }). ALWAYS read_file the matching skill BEFORE executing a covered task. Propose saving improved procedures as new SKILL files. Exempt from reset_vfs; delete via rm.',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
export const SYSTEM_PROMPT_RULES = [
|
||||||
|
'RULES:',
|
||||||
|
'- PRICES: user input is GROSS, API requires NET. Always use net_gross_calc (4-6 decimals): net = gross / (1 + taxRate / 100).',
|
||||||
|
'- LABELS (/labeltemplate): Dashback JSON `{ "objects": [ ... ] }`. Do NOT use ZPL.',
|
||||||
|
'- IMAGES: flow search_images → analyze_image({ paths, prompt }) → upload via fetch in javascript_execute. `image/` is READ-ONLY; write generated images elsewhere (e.g. "images/…").',
|
||||||
|
'- OUTPUT: Markdown + emoji. Render VFS images inline ``; other files (PDF, JSON, CSV) need download links.',
|
||||||
|
'- NAVIGATION: when the user asks to view/open a page or entity, call set-user-route with a path from the matrix.',
|
||||||
|
'- API: routes are not inlined. BEFORE dashback_request, read_file the api-<section>.SKILL for exact params and response shapes. `api-binary-streaming-routes.SKILL` covers fetch()-only routes.',
|
||||||
|
'- T* TYPES: `T<n>` (e.g. T2) is shorthand for a reusable object shape. Ids are GLOBAL: the same T<n> always means the same shape in every skill. Each api-<section>.SKILL only lists, in its "Reusable types" block, the T<n> it uses (plus the ones those reference), so a skill shows a subset of the registry, and a T<n> may appear in several skills with an identical definition.',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
export const SYSTEM_PROMPT_BINARY_UPLOAD = [
|
||||||
|
'BINARY IMAGE UPLOAD (fetch() in javascript_execute, image/avif buffer body):',
|
||||||
|
'- POST /article/{sku}/image -> 201 { image: { picId, sort, full: { w, h, size }, thumb: { w, h }, hash, thumbHash } }',
|
||||||
|
'- POST /category/{id}/image -> 201 { category: { id, thumbW, thumbH, thumbHash } }',
|
||||||
|
'- POST /brand/{id}/image -> 201 { brand: { id, fullHash, thumbW, thumbH, thumbHash, pics: [...] } }',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
export const SYSTEM_PROMPT_ROUTE_MATRIX = [
|
||||||
|
'ROUTE MATRIX (client routes German, API sections English; skills = api-<section>):',
|
||||||
|
'| Destination | Path | Example | API sections |',
|
||||||
|
'|:---|:---|:---|:---|',
|
||||||
|
'| Article Search | `/artikel?target={query}&filter={filter}&offset={n}` | `/artikel?target=kabel` | article, category, brand |',
|
||||||
|
'| Article Detail | `/artikel/${encodeURIComponent(sku)}` | `/artikel/ART-10042` | article, category, brand, attributename, propertyname, unit, supplier, printer, labeltemplate, ledger |',
|
||||||
|
'| Warehouse / Stock | `/artikel/lager` | `/artikel/lager` | warehouse, stock, ledger |',
|
||||||
|
'| Brands | `/artikel/marken` | `/artikel/marken` | brand |',
|
||||||
|
'| Label Templates | `/artikel/vorlagen` | `/artikel/vorlagen` | labeltemplate, printer |',
|
||||||
|
'| Attributes | `/artikel/merkmale` | `/artikel/merkmale` | attributename, attributevalue, propertyname |',
|
||||||
|
'| Category Root | `/kategorien` | `/kategorien` | category |',
|
||||||
|
'| Category Subtree | `/kategorien/${slug1}/${slug2}` (encoded slugs) | `/kategorien/elektronik/kabel` | category, article |',
|
||||||
|
'| Delivery List | `/lieferung` | `/lieferung` | delivery |',
|
||||||
|
'| New Delivery | `/lieferung/neu` | `/lieferung/neu` | delivery, supplier, article, warehouse, stock |',
|
||||||
|
'| Delivery Detail | `/lieferung/${deliveryId}` | `/lieferung/42` | delivery, supplier, ledger |',
|
||||||
|
'| Suppliers | `/lieferung/lieferanten` | `/lieferung/lieferanten` | supplier |',
|
||||||
|
'| Quote List | `/auftrag` | `/auftrag` | quote |',
|
||||||
|
'| New Quote | `/auftrag/neu` | `/auftrag/neu` | quote, entity, article |',
|
||||||
|
'| Quote Detail | `/auftrag/${quoteId}` | `/auftrag/1001` | quote, entity |',
|
||||||
|
'| Order List | `/auftrag/bestellung` | `/auftrag/bestellung` | order |',
|
||||||
|
'| New Order | `/auftrag/bestellung/neu` | `/auftrag/bestellung/neu` | order, entity, article |',
|
||||||
|
'| Order Detail | `/auftrag/bestellung/${orderId}` | `/auftrag/bestellung/2005` | order, entity, account, payment, shipping, package |',
|
||||||
|
'| Customer List | `/kunde` | `/kunde` | entity |',
|
||||||
|
'| New Customer | `/kunde/neu` | `/kunde/neu` | entity, customergroup, number |',
|
||||||
|
'| Customer Detail | `/kunde/${entityId}` | `/kunde/312` | entity, customergroup |',
|
||||||
|
'| Customer Groups | `/kunde/gruppen` | `/kunde/gruppen` | customergroup |',
|
||||||
|
'| Article History | `/infos/articlehistory` | `/infos/articlehistory` | article (`GET /article/{sku}/history`) |',
|
||||||
|
'| Global History | `/infos/globalhistory` | `/infos/globalhistory` | company (`GET /company/history`) |',
|
||||||
|
'| Statistics | `/infos/statistics` | `/infos/statistics` | none; aggregate from order, delivery, ledger |',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Skill Index
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the SKILL index block for the system prompt.
|
||||||
|
*
|
||||||
|
* Listing the skills inline means the model knows what guidance exists BEFORE it decides what
|
||||||
|
* to do, so it can reach for `read_file` on the right skill instead of guessing a procedure
|
||||||
|
* from scratch. Only the filename, title and line count are included — the body is read on
|
||||||
|
* demand, which keeps the prompt small no matter how many skills exist.
|
||||||
|
*
|
||||||
|
* @param {string} vfsRoot
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function buildSkillIndex(vfsRoot) {
|
||||||
|
if (!vfsRoot) {
|
||||||
|
return 'AVAILABLE SKILLS: unknown (the virtual filesystem could not be read this turn). Call find_files({ skills: true }) to check.';
|
||||||
|
}
|
||||||
|
|
||||||
|
let skills;
|
||||||
|
try {
|
||||||
|
skills = listSkillFiles(vfsRoot);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('conversation: failed to list skill files:', err.message);
|
||||||
|
return 'AVAILABLE SKILLS: could not be read this turn. Call find_files({ skills: true }) to check.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skills.length === 0) {
|
||||||
|
return [
|
||||||
|
'AVAILABLE SKILLS: none. This company has no SKILL files yet.',
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = Math.max(...skills.map((s) => s.path.length));
|
||||||
|
return [
|
||||||
|
`AVAILABLE SKILLS (${skills.length}): stored in the VFS root as flat <slug>.SKILL files.`,
|
||||||
|
'Before starting a task that any of these cover, read the matching skill with read_file and follow it.',
|
||||||
|
'',
|
||||||
|
...skills.map((s) => ` ${s.path.padEnd(width)} ${String(s.lines).padStart(4)} lines | ${s.title}`),
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region System Prompt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System prompt: base instructions plus the generated API routes reference
|
||||||
|
* (docs/api/routes.md), read on each run so it stays current.
|
||||||
|
*
|
||||||
|
* @param {string} vfsRoot Resolved VFS root for this user's company, used to list SKILL files.
|
||||||
|
* @param {number} [googleAuthId]
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
export async function buildSystemPrompt(vfsRoot, googleAuthId) {
|
||||||
|
let prefix = '';
|
||||||
|
let suffix = '';
|
||||||
|
if (googleAuthId) {
|
||||||
|
try {
|
||||||
|
const prefixSetting = await dbCompanySetting.getByKey(googleAuthId, 'systemprompt-prefix', { maskWriteonly: false });
|
||||||
|
prefix = prefixSetting?.value?.trim() || '';
|
||||||
|
} catch (err) {
|
||||||
|
console.error('conversation: failed to read systemprompt-prefix:', err.message);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const suffixSetting = await dbCompanySetting.getByKey(googleAuthId, 'systemprompt-suffix', { maskWriteonly: false });
|
||||||
|
suffix = suffixSetting?.value?.trim() || '';
|
||||||
|
} catch (err) {
|
||||||
|
console.error('conversation: failed to read systemprompt-suffix:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
const basePrompt = [
|
||||||
|
'QuixPOS Article Management System API Assistant',
|
||||||
|
`Date: ${today}`,
|
||||||
|
'[TESTSYSTEM, destructive commands are allowed, main purpose of this system is to find bugs]',
|
||||||
|
SYSTEM_PROMPT_TOOLS,
|
||||||
|
'',
|
||||||
|
SYSTEM_PROMPT_VFS_RULES,
|
||||||
|
'',
|
||||||
|
buildSkillIndex(vfsRoot),
|
||||||
|
'',
|
||||||
|
SYSTEM_PROMPT_RULES,
|
||||||
|
'',
|
||||||
|
SYSTEM_PROMPT_BINARY_UPLOAD,
|
||||||
|
'',
|
||||||
|
SYSTEM_PROMPT_ROUTE_MATRIX,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const sections = [];
|
||||||
|
if (prefix) sections.push(prefix);
|
||||||
|
sections.push(basePrompt);
|
||||||
|
if (suffix) sections.push(suffix);
|
||||||
|
return sections.join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
@@ -4,6 +4,7 @@ import { defineTool, ToolError } from 'libllmorbiter';
|
|||||||
import { isSkillPath, parseSkill, SKILL_EXT } from './skill.js';
|
import { isSkillPath, parseSkill, SKILL_EXT } from './skill.js';
|
||||||
import { getApiRouteSkill } from './routes-skills.js';
|
import { getApiRouteSkill } from './routes-skills.js';
|
||||||
import { prepareVfs } from './vfs.js';
|
import { prepareVfs } from './vfs.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 400;
|
const DEFAULT_LIMIT = 400;
|
||||||
const MAX_LIMIT = 5000;
|
const MAX_LIMIT = 5000;
|
||||||
@@ -61,10 +62,7 @@ function looksBinary(buf) {
|
|||||||
export function createReadFileTool({ googleAuthId }) {
|
export function createReadFileTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'read_file',
|
name: 'read_file',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.read_file,
|
||||||
'Read text file from VFS. Root-relative forward-slash paths. ' +
|
|
||||||
'SKILL files return parsed title and markdown content. ' +
|
|
||||||
'Windowed via seek/limit (0-indexed lines). Binary files not supported (use analyze_image for images).',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -73,15 +71,15 @@ export function createReadFileTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
filename: {
|
filename: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'VFS file path.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.read_file.filename,
|
||||||
},
|
},
|
||||||
seek: {
|
seek: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: 'Line offset (0-indexed) or null. Default 0.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.read_file.seek,
|
||||||
},
|
},
|
||||||
limit: {
|
limit: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: `Max lines to return or null. Default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}.`,
|
description: TOOL_PARAMETER_DESCRIPTIONS.read_file.limit,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from 'path';
|
|||||||
import { defineTool } from 'libllmorbiter';
|
import { defineTool } from 'libllmorbiter';
|
||||||
import { isSkillPath } from './skill.js';
|
import { isSkillPath } from './skill.js';
|
||||||
import { resolveVfsRoot } from './vfs.js';
|
import { resolveVfsRoot } from './vfs.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reset_vfs — clears throwaway artifacts out of the virtual filesystem.
|
* reset_vfs — clears throwaway artifacts out of the virtual filesystem.
|
||||||
@@ -26,7 +27,7 @@ import { resolveVfsRoot } from './vfs.js';
|
|||||||
export function createResetVfsTool({ googleAuthId }) {
|
export function createResetVfsTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'reset_vfs',
|
name: 'reset_vfs',
|
||||||
description: 'Clear VFS files, images, and artifacts. Preserves SKILL files by default.',
|
description: TOOL_DESCRIPTIONS.reset_vfs,
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -34,7 +35,7 @@ export function createResetVfsTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
include_skills: {
|
include_skills: {
|
||||||
type: ['boolean', 'null'],
|
type: ['boolean', 'null'],
|
||||||
description: 'Delete SKILL files too (or null). Default false.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.reset_vfs.include_skills,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from 'path';
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import { isReadOnlyDatabasePath, prepareVfs } from './vfs.js';
|
import { isReadOnlyDatabasePath, prepareVfs } from './vfs.js';
|
||||||
import { isSkillPath } from './skill.js';
|
import { isSkillPath } from './skill.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rm — deletes a single file from the virtual filesystem (VFS).
|
* rm — deletes a single file from the virtual filesystem (VFS).
|
||||||
@@ -64,7 +65,7 @@ export function deleteVfsFile(vfsRoot, relPath) {
|
|||||||
export function createRmTool({ googleAuthId }) {
|
export function createRmTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'rm',
|
name: 'rm',
|
||||||
description: 'Delete single VFS file (e.g. specific artifact or SKILL file). Irreversible.',
|
description: TOOL_DESCRIPTIONS.rm,
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -73,7 +74,7 @@ export function createRmTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
filename: {
|
filename: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'VFS file path to delete.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.rm.filename,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import { getStore, getProvider } from '../db/store.js';
|
import { getStore, getProvider } from '../db/store.js';
|
||||||
|
import { TOOL_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 3;
|
const DEFAULT_LIMIT = 3;
|
||||||
const MAX_LIMIT = 20;
|
const MAX_LIMIT = 20;
|
||||||
@@ -23,9 +24,7 @@ function parseWords(input) {
|
|||||||
export function createSearchArticlesTool({ googleAuthId, companyId }) {
|
export function createSearchArticlesTool({ googleAuthId, companyId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'search_articles',
|
name: 'search_articles',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.search_articles,
|
||||||
'Text search articles by name, brand, EAN, SKU, or supplier SKU. ' +
|
|
||||||
`Returns window of full articles (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}), offset, and total count. Ordered alphabetically by name.`,
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import sharp from 'sharp';
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import * as dbCompanySetting from '../db/companysetting.js';
|
import * as dbCompanySetting from '../db/companysetting.js';
|
||||||
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
import { prepareVfs, syncVfsToDb } from './vfs.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const SERPER_ENDPOINT = 'https://google.serper.dev/images';
|
const SERPER_ENDPOINT = 'https://google.serper.dev/images';
|
||||||
const SEARCH_TIMEOUT_MS = 15_000;
|
const SEARCH_TIMEOUT_MS = 15_000;
|
||||||
@@ -136,8 +137,7 @@ async function toAvif(img, { signal } = {}) {
|
|||||||
export function createSearchImagesTool({ googleAuthId }) {
|
export function createSearchImagesTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'search_images',
|
name: 'search_images',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.search_images,
|
||||||
'Search Google Images and download candidates to VFS as AVIF. Returns VFS paths for inspection via analyze_image.',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -146,23 +146,23 @@ export function createSearchImagesTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
note: {
|
note: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'A note explaining what this image search is doing (or null). This note will be displayed to the user while executing.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.search_images.note,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Search query.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.search_images.query,
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
type: ['number', 'null'],
|
type: ['number', 'null'],
|
||||||
description: `Candidate count or null. Default ${DEFAULT_COUNT}, max ${MAX_COUNT}.`,
|
description: TOOL_PARAMETER_DESCRIPTIONS.search_images.count,
|
||||||
},
|
},
|
||||||
gl: {
|
gl: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'Country code or null. Default "de".',
|
description: TOOL_PARAMETER_DESCRIPTIONS.search_images.gl,
|
||||||
},
|
},
|
||||||
hl: {
|
hl: {
|
||||||
type: ['string', 'null'],
|
type: ['string', 'null'],
|
||||||
description: 'Language code or null. Default "de".',
|
description: TOOL_PARAMETER_DESCRIPTIONS.search_images.hl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { defineTool } from 'libllmorbiter';
|
import { defineTool } from 'libllmorbiter';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the set-user-route tool.
|
* Creates the set-user-route tool.
|
||||||
@@ -10,8 +11,7 @@ import { defineTool } from 'libllmorbiter';
|
|||||||
export function createSetUserRouteTool({ onRoute } = {}) {
|
export function createSetUserRouteTool({ onRoute } = {}) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'set-user-route',
|
name: 'set-user-route',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.set_user_route,
|
||||||
'Navigate the user to a frontend route. Use this when the user says "show me the article" or asks to see, open, or view a specific page or entity (e.g. an article detail page, orders, quotes, deliveries, or customer). Refer to the User Perspective Route Construction Matrix for valid route patterns.',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -20,7 +20,7 @@ export function createSetUserRouteTool({ onRoute } = {}) {
|
|||||||
properties: {
|
properties: {
|
||||||
route: {
|
route: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Target frontend route path according to the Route Construction Matrix (e.g. "/artikel/ART-10042", "/kategorien/elektronik/kabel", "/auftrag/1001").',
|
description: TOOL_PARAMETER_DESCRIPTIONS.set_user_route.route,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import * as dbCompanySetting from '../db/companysetting.js';
|
import * as dbCompanySetting from '../db/companysetting.js';
|
||||||
|
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const SERPER_SCRAPE_ENDPOINT = 'https://scrape.serper.dev';
|
const SERPER_SCRAPE_ENDPOINT = 'https://scrape.serper.dev';
|
||||||
const SCRAPE_TIMEOUT_MS = 25_000;
|
const SCRAPE_TIMEOUT_MS = 25_000;
|
||||||
@@ -63,7 +64,7 @@ async function serperScrape(url, apiKey, { signal } = {}) {
|
|||||||
export function createUrlGetTool({ googleAuthId }) {
|
export function createUrlGetTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'url_get',
|
name: 'url_get',
|
||||||
description: 'Fetch and scrape web page content via Serper Scrape (text, markdown, metadata).',
|
description: TOOL_DESCRIPTIONS.url_get,
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -72,7 +73,7 @@ export function createUrlGetTool({ googleAuthId }) {
|
|||||||
properties: {
|
properties: {
|
||||||
url: {
|
url: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Target URL.',
|
description: TOOL_PARAMETER_DESCRIPTIONS.url_get.url,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineTool, ToolError } from 'libllmorbiter';
|
import { defineTool, ToolError } from 'libllmorbiter';
|
||||||
import * as dbCompanySetting from '../db/companysetting.js';
|
import * as dbCompanySetting from '../db/companysetting.js';
|
||||||
|
import { TOOL_DESCRIPTIONS } from './prompts.js';
|
||||||
|
|
||||||
const SERPER_SEARCH_ENDPOINT = 'https://google.serper.dev/search';
|
const SERPER_SEARCH_ENDPOINT = 'https://google.serper.dev/search';
|
||||||
const SEARCH_TIMEOUT_MS = 15_000;
|
const SEARCH_TIMEOUT_MS = 15_000;
|
||||||
@@ -88,8 +89,7 @@ async function serperWebSearch(query, apiKey, { gl = 'de', hl = 'de', tbs, page
|
|||||||
export function createWebSearchTool({ googleAuthId }) {
|
export function createWebSearchTool({ googleAuthId }) {
|
||||||
return defineTool({
|
return defineTool({
|
||||||
name: 'web_search',
|
name: 'web_search',
|
||||||
description:
|
description: TOOL_DESCRIPTIONS.web_search,
|
||||||
'Search Google via Serper.dev. Use timePeriod "day" or "week" for news/recent events. Returns 10 results per page.',
|
|
||||||
strict: false,
|
strict: false,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|||||||
Reference in New Issue
Block a user