This commit is contained in:
seb
2026-10-06 03:30:31 +02:00
parent 0369b4e819
commit d5e58e76e1
15 changed files with 103 additions and 196 deletions

View File

@@ -105,52 +105,51 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) {
}
const today = new Date().toISOString().split('T')[0];
const basePrompt = [
'You are an intelligent assistant specialized in exploring, querying, and managing data from the Dashback REST API.',
`Today's date: ${today}`,
'Assistant for QuixPOS Article Management System REST API operation',
`Date: ${today}`,
'',
'YOUR TOOLS:',
'1. dashback_request: Execute HTTP requests (GET, POST, PUT, PATCH, DELETE) against Dashback endpoints.',
'2. ask_user: Ask clarifying questions or request decisions from the user if their intent is ambiguous or requires choices. When you call ask_user, execution pauses immediately and waits for the user to provide their response.',
'3. javascript_execute: Run JavaScript snippets in a sandbox to compute, aggregate, transform, or analyze data. Assign module.exports to return a value. Snippets can call Dashback API routes using fetch() (e.g. await fetch("/article/2")). A sandboxed virtual filesystem (fs / vfs) is available to store and retrieve artifacts across runs.',
'4. find_files: List what is stored in the virtual filesystem (VFS). Call find_files({ skills: true }) to list SKILL files only.',
'5. read_file: Read a VFS file as text, windowed with seek/limit for large files.',
'6. patch: Create, overwrite, or search-and-replace text in a VFS file. This is the only text write path — it also creates SKILL files.',
'7. search_images: Search Google Images and store the candidates in the VFS as AVIF files.',
'8. analyze_image: Have a vision model look at up to 9 images (VFS paths or "image/<fullHash>") and return its judgement. This is how you "see" — never claim to have inspected an image yourself.',
'9. web_search: Search the web using Google Search. Returns organic results, snippets, and knowledge graph. Supports filtering by timePeriod ("day", "week", "month", "year") — always use "day" or "week" for news and recent events.',
'10. url_get: Fetch and scrape the content and markdown of a web page/URL.',
'11. pdf_read: Read and convert PDF or text files. Converts PDF to layout-preserved text ("txt") or renders pages into PNG images ("png") saved in the VFS images/ folder.',
'12. reset_vfs: Delete files, images, and artifacts stored in the virtual filesystem (VFS) to free up storage or clean up. SKILL files are exempt and survive.',
'13. rm: Delete a single file from the VFS. This is how a SKILL file is deleted on purpose, since reset_vfs always spares skills.',
'14. apisuggestionsorissues: Report missing API endpoints, bugs, or issues to be triaged. Automatically writes an entry to the company history table for review.',
'1. dashback_request: HTTP requests (GET, POST, PUT, PATCH, DELETE) against Dashback endpoints.',
'2. ask_user: Prompt user for clarification or decisions. Pauses execution until response.',
'3. javascript_execute: Sandboxed Node environment for computation, aggregation, and binary I/O. Assign module.exports. API: fetch("/path"). VFS: fs/vfs.',
'4. find_files: List VFS files. Set skills=true for SKILL files.',
'5. read_file: Read VFS text file (seek/limit windowed).',
'6. patch: Create, overwrite, or replace text in VFS files (only text write path; creates SKILL files).',
'7. search_images: Search Google Images; downloads AVIF candidates to VFS.',
'8. analyze_image: Inspect up to 9 images via vision model (VFS paths or "image/<hash>"). Always evaluate images through this tool.',
'9. web_search: Web search via Google. Filter with timePeriod ("day"/"week" for current events).',
'10. url_get: Fetch and scrape web page content/markdown.',
'11. pdf_read: Convert PDF/text to layout text ("txt") or VFS page images ("png").',
'12. reset_vfs: Delete VFS files/artifacts. Preserves SKILL files.',
'13. rm: Delete single VFS file (used to delete SKILL files or specific artifacts).',
'14. apisuggestionsorissues: Report missing API endpoints, bugs, or issues for triage.',
'',
'VIRTUAL FILESYSTEM:',
'- You have a persistent per-company VFS. Paths are relative to the VFS root and always use forward slashes, e.g. "labels/sheet1.png".',
'- To READ text: find_files (list) then read_file (content). To WRITE text: patch. Binary artifacts (images, PDFs, buffers) go through javascript_execute.',
'- Never invent a VFS path. Always confirm with find_files before reading or referencing a file.',
'- Persistent per-company VFS. Paths are root-relative forward-slash (e.g. "labels/sheet1.png").',
'- Text: list with find_files, read with read_file, write with patch. Binary/buffers: javascript_execute.',
'- Never invent paths; verify with find_files before reading or referencing.',
'',
'SKILL FILES:',
'- A SKILL file is a reusable instruction document: a file named "<slug>.SKILL" at the VFS root whose FIRST LINE is its title and whose remaining lines are markdown.',
'- SKILL file names may NOT contain "/" — skills are flat, at the root only. Use find_files({ skills: true }) to discover them; this listing is not affected by pagination or by how many artifacts exist.',
'- Read a skill with read_file BEFORE starting a task it covers, and follow it. If you learn a better procedure during the task, offer to write it back as a new SKILL file.',
'- SKILL files are exempt from reset_vfs: artifacts are disposable, skills are not. Never call reset_vfs expecting to clear skills, and never put throwaway output in a .SKILL file.',
'- To delete a skill (or any single file) use rm — it is the only way to remove one. Deletion is final.',
'- Reusable instruction docs: "<slug>.SKILL" at VFS root. First line is title, followed by markdown.',
'- Root-only (no "/"). Discover via find_files({ skills: true }).',
'- Read with read_file BEFORE starting covered tasks; follow instructions. Propose saving improved procedures as new SKILL files.',
'- Exempt from reset_vfs. Delete permanently via rm.',
'',
buildSkillIndex(vfsRoot),
'',
'IMAGES:',
'- You cannot see images yourself. To evaluate one, call analyze_image and report what the vision model says.',
'- Typical flow: search_images("query") → analyze_image({ paths: [...candidates], prompt: "…" }) → upload the winner with dashback_request.',
'- The `image/` folder holds database images and is READ-ONLY. Generated images go anywhere else (e.g. "images/…").',
'- Evaluate images via analyze_image; report model findings.',
'- Flow: search_images("query") → analyze_image({ paths: [...], prompt: "…" }) → upload via dashback_request.',
'- `image/` is READ-ONLY database storage. Write generated images elsewhere (e.g. "images/…").',
'',
'OPERATING GUIDELINES:',
'- Refer to the DASHBACK API ROUTES documentation below to find appropriate endpoints, parameters, and HTTP methods.',
'- SELLING PRICES: In article responses (GET /article/{sku} and returned article), the selling price field is named net (NOT price). The structure of entries in article.prices is { minmultiplier, net, taxRate, customergroupId }.',
'- GROSS vs NET PRICES: Users almost always mean GROSS (brutto, incl. VAT) prices when they specify a price (e.g. "1,60 €"), but the API stores and accepts NET prices only. Convert: net = gross / (1 + taxRate/100). Send net with high precision (at least 4–6 decimal places) so rounding back gives the exact gross. Calculate this in javascript_execute.',
'- DATA TYPES & WIRE FORMAT: Dashback strictly validates types (numeric values like weight, price, quantity, IDs must be numbers; flags must be booleans).',
'- SHOWING GENERATED IMAGES: Markdown images ARE supported and render inline in the chat. After writing an image into the virtual filesystem (e.g. `fs.writeFileSync("artifacts/example.png", buf)`), reference the exact path you wrote with standard markdown `![title](artifacts/example.png)`, which renders inline. Make the alt text descriptive. Other file types (PDF, JSON, CSV) are NOT rendered inline — link to them instead and offer them as a download.',
'- LABEL TEMPLATES (/labeltemplate): Label templates are NOT ZPL. content is Dashback\'s JSON label schema: { "objects": [ ... ] }. Do NOT write or suggest ZPL.',
'- Format responses clearly using markdown tables, bullet lists, or bold key-value pairs where appropriate.',
'- Endpoint reference: see DASHBACK API ROUTES below.',
'- SELLING PRICES: In article responses (GET /article/{sku}), selling price field is `net` (NOT `price`). Schema in `article.prices`: { minmultiplier, net, taxRate, customergroupId }.',
'- GROSS vs NET PRICES: User prices are almost always GROSS (incl. VAT); API requires NET. Formula: `net = gross / (1 + taxRate / 100)`. Compute in javascript_execute with high precision (4–6 decimals) to prevent rounding error.',
'- WIRE TYPES: Strict validation (IDs, weight, price, quantity must be numbers; flags booleans).',
'- DISPLAYING GENERATED IMAGES: Inline markdown images supported. Embed written VFS image paths: `![descriptive alt](artifacts/example.png)`. Other files (PDF, JSON, CSV) do not render inline; provide download links.',
'- LABEL TEMPLATES (/labeltemplate): Must use Dashback JSON schema `{ "objects": [ ... ] }`. Do NOT use ZPL.',
'- Format output with markdown tables, bullet points, or bold key-value pairs.',
...(routesDoc ? ['', 'DASHBACK API ROUTES:', routesDoc] : []),
].join('\n');

View File

@@ -261,12 +261,8 @@ export function createAnalyzeImageTool({ googleAuthId, model }) {
return defineTool({
name: 'analyze_image',
description:
'Look at one or up to 9 images with a vision model and get its written judgement back. ' +
'Paths are VFS-relative (e.g. "images/bild.de-1.avif" from search_images) or ' +
'"image/<fullHash>" for a database image. ' +
'Passing several paths renders them as a labelled grid in one call — use that to compare ' +
'search candidates side by side. The returned text is the vision model\'s answer, so judge from it ' +
'rather than claiming to have seen the image yourself.',
'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,
parameters: {
type: 'object',
@@ -275,23 +271,19 @@ export function createAnalyzeImageTool({ googleAuthId, model }) {
properties: {
paths: {
type: 'array',
description:
'1–9 image paths. VFS files (e.g. ["images/a-1.avif"]) or database images (e.g. ["image/<fullHash>"]). ' +
'Several paths are rendered as one labelled grid.',
description: '1–9 VFS paths or DB paths ("image/<hash>").',
items: { type: 'string' },
minItems: 1,
maxItems: MAX_IMAGES,
},
prompt: {
type: 'string',
description:
'What to inspect or compare, e.g. "Which of these is a clean product shot on white, ' +
'without watermark or text overlay?"',
description: 'Inspection criteria or question.',
},
grid: {
type: 'string',
enum: Object.keys(GRID_CONFIGS),
description: 'Force a grid layout. Omit to pick one from the image count.',
description: 'Grid layout. Omit for auto.',
},
},
},

View File

@@ -11,10 +11,7 @@ import * as db from '../db/company.js';
export function createApiSuggestionsOrIssuesTool({ googleAuthId, companyId }) {
return defineTool({
name: 'apisuggestionsorissues',
description:
'Report a missing API endpoint, feature request, bug, or general issue. ' +
'The report is recorded in the company history table for review by the triage agent. ' +
'Provide a clear description of the missing functionality or issue.',
description: 'Report missing API endpoint, feature request, bug, or issue for triage.',
strict: false,
parameters: {
type: 'object',
@@ -23,16 +20,16 @@ export function createApiSuggestionsOrIssuesTool({ googleAuthId, companyId }) {
properties: {
text: {
type: 'string',
description: 'Detailed description of the missing API, bug, or issue.',
description: 'Issue or request details.',
},
type: {
type: 'string',
enum: ['missing_api', 'bug', 'suggestion', 'issue', 'other'],
description: 'Category/type of the report.',
description: 'Report category.',
},
title: {
type: 'string',
description: 'Optional short summary or title.',
description: 'Short summary.',
},
},
},

View File

@@ -14,8 +14,7 @@ export function createAskUserTool() {
return defineTool({
name: 'ask_user',
description:
'Ask the user when you need clarification or a decision (e.g. which SKU or ID they meant, or what parameter to use). ' +
'Give a concise question + up to 3 short options (A,B,C); the user may select an option or provide free-form text.',
'Prompt user for clarification or decision with concise question and up to 3 options.',
strict: false,
parameters: {
type: 'object',
@@ -24,11 +23,11 @@ export function createAskUserTool() {
properties: {
question: {
type: 'string',
description: 'Question for the user.',
description: 'Question prompt.',
},
options: {
type: 'array',
description: 'Up to 3 short option labels.',
description: 'Up to 3 option labels.',
items: { type: 'string' },
maxItems: 3,
},

View File

@@ -14,19 +14,17 @@ const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
export function createDashbackRequestTool({ googleAuthId }) {
return defineTool({
name: 'dashback_request',
description:
'Interrogate and call the Dashback REST API. Supports GET requests directly. ' +
'Paths are relative to the API root (e.g. "/article/2", "/category/tree", "/brand").',
description: 'Call Dashback REST API. Paths are root-relative (e.g. "/article/2").',
strict: false,
parameters: {
type: 'object',
additionalProperties: false,
required: ['method', 'path'],
properties: {
method: { type: 'string', enum: METHODS, description: 'HTTP method' },
path: { type: 'string', description: 'Path of the endpoint to request (e.g. "/article/2", "/category/tree", "/brand").' },
query: { type: 'object', description: 'Optional query parameters as key-value pairs (e.g. { limit: 10, offset: 0 }).' },
body: { type: 'string', description: 'Raw JSON request payload string (e.g. \'{"weight": 2.5}\').' },
method: { type: 'string', enum: METHODS, description: 'HTTP method.' },
path: { type: 'string', description: 'Endpoint path (e.g. "/article/2").' },
query: { type: 'object', description: 'Query parameters object.' },
body: { type: 'string', description: 'JSON request payload string.' },
},
},
invoke: async ({ method = 'GET', path, query = null, body = null }) => {

View File

@@ -81,13 +81,8 @@ export function createFindFilesTool({ googleAuthId }) {
return defineTool({
name: 'find_files',
description:
'List files stored in the virtual filesystem (VFS). ' +
'VFS paths are relative to the VFS root and use forward slashes, e.g. "labels/sheet1.png". ' +
'Set skills=true to list ONLY the SKILL files: reusable, hand-authored instruction ' +
'documents that are EXEMPT from reset_vfs. A SKILL file is a flat, root-level file ' +
'named "<slug>.SKILL" whose first line is its title and whose remaining lines are markdown. ' +
'Read a skill with read_file before acting on a task it covers. ' +
'Pass no arguments to list the VFS root.',
'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,
parameters: {
type: 'object',
@@ -95,23 +90,19 @@ export function createFindFilesTool({ googleAuthId }) {
properties: {
dirname: {
type: 'string',
description:
'Directory to list, relative to the VFS root (e.g. "labels"). Defaults to the root ".". ' +
'Must be "." or empty when skills=true — SKILL files always live at the root.',
description: 'VFS directory to list. Default "." (root). Must be "." or empty if skills=true.',
},
recursive: {
type: 'boolean',
description: 'true = walk the whole subtree. Default false (immediate contents only).',
description: 'Walk subdirectories. Default false.',
},
skills: {
type: 'boolean',
description:
'true = return ONLY SKILL files at the VFS root with their titles parsed. ' +
'Overrides dirname/recursive. Default false.',
description: 'List only root SKILL files with parsed titles. Overrides dirname/recursive. Default false.',
},
seek: {
type: 'number',
description: 'Entries to skip (pagination). Default 0.',
description: 'Pagination offset. Default 0.',
},
limit: {
type: 'number',
@@ -119,7 +110,7 @@ export function createFindFilesTool({ googleAuthId }) {
},
maxDepth: {
type: 'number',
description: 'Max directory depth to descend when recursive=true. Default 3.',
description: 'Max recursive directory depth. Default 3.',
},
},
},

View File

@@ -122,29 +122,11 @@ export function createJavascriptExecuteTool({ googleAuthId }) {
return defineTool({
name: 'javascript_execute',
description:
'Run JavaScript in a sandboxed Node environment and return the result. ' +
'Runs in an isolated worker thread with a hard wall-clock timeout. ' +
'Return a value by assigning `module.exports` (sync value or a promise). ' +
'For async code: `module.exports = (async () => { ... })()`. ' +
'console.* is captured and returned as stdout. ' +
'To query the Dashback REST API from JavaScript: call `await fetch("/route")` (e.g. `const res = await fetch("/article/2"); const data = await res.json();`). ' +
'Relative paths automatically resolve against Dashback and are authenticated with your user credentials. ' +
'A virtual filesystem (fs / vfs) is available to store and retrieve artifacts (files, images, JSON data, buffers) across runs (e.g. `fs.writeFileSync("artifact.json", data)`). ' +
'IMAGES WRITTEN TO THE VFS ARE DISPLAYED INLINE IN THE CHAT. After writing an image into the virtual filesystem (e.g. `fs.writeFileSync("artifacts/example.png", buf)`), ' +
'reference the exact path you wrote with standard markdown `![title](artifacts/example.png)` and it renders as an image inline. ' +
'Note the `image/` folder is reserved for database images and is read-only; write generated images anywhere else. ' +
'Always embed images you generate this way so the user sees them. Other file types do not render inline. ' +
'For TEXT use the dedicated find_files / read_file / patch tools instead of hand-rolling fs calls — they validate paths and persist to the database. ' +
'Use this sandbox for BINARY work and computation: images, PDFs, buffers, charts, encoding. ' +
'To find or inspect images, use the search_images and analyze_image tools — they are not available inside the sandbox. ' +
'The `sharp` image library is a pre-injected GLOBAL. ' +
'Also global: `validateLabelObjects(objects)` and `validateLabelObject(object)` from react-canvas-label-editor. ' +
'CRYPTO: only the WebCrypto global `crypto` is available. Node\'s `crypto` module is NOT installed, so ' +
'`require("crypto")` will fail. Available: `crypto.randomUUID()`, `crypto.getRandomValues(typedArray)`, ' +
'and the async `crypto.subtle` API (digest/hash, sign/verify, encrypt/decrypt, importKey/exportKey, deriveBits). ' +
'`crypto.subtle` is async, so use it inside an async IIFE: ' +
'`module.exports = (async () => (await crypto.subtle.digest("SHA-256", data)).byteLength)();`. ' +
'Typed arrays work correctly across the sandbox boundary (verified against Node\'s real digests).',
'Execute JS in isolated Node worker sandbox. Assign `module.exports` to return value (sync value or Promise; no bare top-level await — wrap async in IIFE: `module.exports = (async () => { ... })()`). ' +
'REST API: `await fetch("/path")` (authenticated). ' +
'VFS: `fs.readFileSync`/`writeFileSync` for binary artifacts/buffers/images (`image/` is read-only). ' +
'Embed generated images in markdown: `![alt](path)`. Use find_files/read_file/patch for text. ' +
'Globals: `fs`/`vfs`, `sharp`, WebCrypto `crypto` (`subtle`, `randomUUID`), web standard globals. Node built-ins unavailable (`require` fails).',
strict: false,
parameters: {
type: 'object',
@@ -153,25 +135,15 @@ export function createJavascriptExecuteTool({ googleAuthId }) {
properties: {
code: {
type: 'string',
description:
'JavaScript source to run in a CommonJS wrapper. Set `module.exports` to return a value; ' +
'for async code assign the PROMISE, e.g. `module.exports = (async () => { ... })()`. ' +
'To query Dashback REST API: `const res = await fetch("/article/2"); const data = await res.json();`. ' +
'To persist BINARY artifacts between runs: use `fs.writeFileSync("name.ext", buffer)` and `fs.readFileSync("name.ext")`. ' +
'For text files prefer the find_files / read_file / patch tools. ' +
'Bare top-level `await` is a syntax error here — always wrap it in that async IIFE. ' +
'Globals available: `fetch`, `console`, `fs`/`vfs`, `sharp`, `performance`, `URL`, `URLSearchParams`, `Headers`, ' +
'`Request`, `Response`, `FormData`, `Blob`, `File`, `TextEncoder`, `TextDecoder`, `AbortController`, ' +
'`AbortSignal`, `setTimeout`, `clearTimeout`, and WebCrypto `crypto` (`randomUUID`, `getRandomValues`, `subtle`). ' +
'No Node built-ins are installable — `require("crypto")`, `require("fs")`, `require("path")` etc. all fail.',
description: 'CommonJS JS code. Assign result to `module.exports`. Async code must use IIFE.',
},
timeout: {
type: 'number',
description: `Hard wall-clock limit in ms. Default ${DEFAULT_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS}.`,
description: `Execution timeout in ms. Default ${DEFAULT_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS}.`,
},
max_output: {
type: 'number',
description: `Max chars of captured console output to return. Default ${DEFAULT_MAX_OUTPUT}.`,
description: `Max stdout chars. Default ${DEFAULT_MAX_OUTPUT}.`,
},
},
},

View File

@@ -67,17 +67,10 @@ export function createPatchTool({ googleAuthId }) {
return defineTool({
name: 'patch',
description:
'Create, overwrite, or search-and-replace text in a virtual filesystem (VFS) file. ' +
'This is the only way to write VFS files — javascript_execute writes stay for bulk binary artifacts.\n' +
'Each op needs "file" and "replace"; "search" selects the mode:\n' +
' - search omitted/null → create the file, or overwrite it entirely (directories are auto-created).\n' +
' - search given → replace the FIRST occurrence of that exact text. "skiplines" skips N lines before\n' +
' matching, which is how you target a later duplicate.\n' +
'Create a SKILL file (title on line 1, markdown below; exempt from reset_vfs):\n' +
'[{"file": "image-pipeline.SKILL", "search": null, "replace": "Image pipeline\\n\\n# Steps\\n1. …"}]\n' +
'or, letting the tool render the title line for you:\n' +
'[{"file": "image-pipeline.SKILL", "search": null, "title": "Image pipeline", "replace": "# Steps\\n1. …"}]\n' +
'Read a file with read_file first and copy its text into "search" verbatim — the match is exact.',
'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,
parameters: {
type: 'object',
@@ -86,7 +79,7 @@ export function createPatchTool({ googleAuthId }) {
properties: {
operations: {
type: 'array',
description: 'Operations, applied in order. If one fails, earlier ones are kept and the error names the op.',
description: 'Ordered patch operations.',
items: {
type: 'object',
additionalProperties: false,
@@ -94,28 +87,23 @@ export function createPatchTool({ googleAuthId }) {
properties: {
file: {
type: 'string',
description:
'VFS path, relative to the root, forward slashes (e.g. "notes.md", "image-pipeline.SKILL"). ' +
'SKILL file names may not contain "/" — they live at the root.',
description: 'VFS path (e.g. "notes.md", "pipeline.SKILL"). SKILL files must be root-level (no "/").',
},
search: {
type: ['string', 'null'],
description: 'Exact existing text to replace. null/omitted = create or overwrite the whole file.',
description: 'Exact text to replace. null/omitted = overwrite or create file.',
},
replace: {
type: 'string',
description:
'Text to write. For a SKILL file you may pass "{ title, markdown }" and the title line is rendered for you.',
description: 'Replacement text.',
},
title: {
type: 'string',
description:
'Optional title for a SKILL file written with a raw markdown string in "replace". ' +
'Becomes line 1 of the file.',
description: 'Optional line-1 title for SKILL files.',
},
skiplines: {
type: 'integer',
description: 'Skip this many lines before searching for "search". Use when the text occurs more than once.',
description: 'Lines to skip before searching.',
},
},
},

View File

@@ -43,9 +43,7 @@ export function createPdfReadTool({ googleAuthId }) {
return defineTool({
name: 'pdf_read',
description:
'Read and convert PDF (or text) documents. ' +
'Supports converting PDF to layout-preserved text ("txt") or rendering pages to PNG images ("png") ' +
'stored in the VFS images/ folder for inspection with analyze_image or inline markdown embedding.',
'Read and convert PDF/text documents to text or VFS PNG images (stored in "images/").',
strict: false,
parameters: {
type: 'object',
@@ -54,23 +52,20 @@ export function createPdfReadTool({ googleAuthId }) {
properties: {
path: {
type: 'string',
description: 'VFS path of the PDF or text file (e.g. "invoice.pdf", "docs/order.pdf"), or HTTP(S) URL.',
description: 'VFS path or HTTP(S) URL.',
},
format: {
type: 'string',
enum: ['txt', 'png', 'both'],
description:
'Conversion format: "txt" extracts layout-preserved text, ' +
'"png" renders pages into PNG images stored in the VFS images/ folder, ' +
'"both" extracts text and renders PNG images. Default is "txt".',
description: 'Output format: "txt" (layout text), "png" (page images), "both". Default "txt".',
},
page: {
type: 'number',
description: 'Specific page number (1-based) to extract text or render as image. If omitted, all pages are processed.',
description: '1-based page number. Omit for all pages.',
},
dpi: {
type: 'number',
description: 'Resolution in DPI for PNG rendering. Default 150.',
description: 'PNG render DPI. Default 150.',
},
},
},

View File

@@ -61,11 +61,9 @@ export function createReadFileTool({ googleAuthId }) {
return defineTool({
name: 'read_file',
description:
'Read a file from the virtual filesystem (VFS). Paths are relative to the VFS root ' +
'and use forward slashes, e.g. "labels/sheet1.png" or "image-pipeline.SKILL". ' +
'Reading a SKILL file returns its `title` (first line) separately from the markdown `content`. ' +
'Large files are windowed with seek/limit (lines, 0-indexed) and the result reports ' +
'how many lines remain. Binary files cannot be read — use analyze_image for images.',
'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,
parameters: {
type: 'object',
@@ -74,15 +72,15 @@ export function createReadFileTool({ googleAuthId }) {
properties: {
filename: {
type: 'string',
description: 'File path relative to the VFS root, using forward slashes (e.g. "image-pipeline.SKILL").',
description: 'VFS file path.',
},
seek: {
type: 'number',
description: 'First line to return, 0-indexed. Default 0.',
description: 'Line offset (0-indexed). Default 0.',
},
limit: {
type: 'number',
description: `Maximum number of lines to return. Default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}.`,
description: `Max lines to return. Default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}.`,
},
},
},

View File

@@ -27,10 +27,7 @@ import { resolveVfsRoot } from './vfs.js';
export function createResetVfsTool({ googleAuthId }) {
return defineTool({
name: 'reset_vfs',
description:
'Delete files, images, and artifacts stored in the virtual filesystem (VFS) to free up storage. ' +
'SKILL files are NOT deleted — reusable instruction documents survive a reset. ' +
'Use find_files({ skills: true }) to see which SKILL files were kept.',
description: 'Clear VFS files, images, and artifacts. Preserves SKILL files by default.',
strict: false,
parameters: {
type: 'object',
@@ -38,8 +35,7 @@ export function createResetVfsTool({ googleAuthId }) {
properties: {
include_skills: {
type: 'boolean',
description:
'Also delete SKILL files. Default false — they are exempt unless you explicitly opt in.',
description: 'Delete SKILL files too. Default false.',
},
},
},

View File

@@ -65,11 +65,7 @@ export function deleteVfsFile(vfsRoot, relPath) {
export function createRmTool({ googleAuthId }) {
return defineTool({
name: 'rm',
description:
'Delete a single file from the virtual filesystem (VFS). ' +
'Use this to remove a SKILL file: reset_vfs intentionally preserves skills, so rm is how a skill is ' +
'deleted on purpose. Also use it to drop one unwanted artifact. ' +
'Deletion is immediate and cannot be undone. Use reset_vfs to clear many files or a directory at once.',
description: 'Delete single VFS file (e.g. specific artifact or SKILL file). Irreversible.',
strict: false,
parameters: {
type: 'object',
@@ -78,9 +74,7 @@ export function createRmTool({ googleAuthId }) {
properties: {
filename: {
type: 'string',
description:
'File to delete, relative to the VFS root using forward slashes ' +
'(e.g. "image-pipeline.SKILL", "notes.md", "labels/sheet1.png").',
description: 'VFS file path to delete.',
},
},
},

View File

@@ -137,10 +137,7 @@ export function createSearchImagesTool({ googleAuthId }) {
return defineTool({
name: 'search_images',
description:
'Search Google Images and store the results in the virtual filesystem as AVIF candidates. ' +
'Returns VFS paths, not inline images — then pass those paths to analyze_image to actually look at them ' +
'(up to 9 at once, rendered as a labelled grid) and pick the best one before uploading it to an article ' +
'or category. Candidates are cached, so a second search of the same term is cheap.',
'Search Google Images and download candidates to VFS as AVIF. Returns VFS paths for inspection via analyze_image.',
strict: false,
parameters: {
type: 'object',
@@ -149,19 +146,19 @@ export function createSearchImagesTool({ googleAuthId }) {
properties: {
query: {
type: 'string',
description: 'Search term, e.g. " Mineralwasser Flasche Glas ".',
description: 'Search query.',
},
count: {
type: 'number',
description: `How many candidate images to return. Default ${DEFAULT_COUNT}, max ${MAX_COUNT}.`,
description: `Candidate count. Default ${DEFAULT_COUNT}, max ${MAX_COUNT}.`,
},
gl: {
type: 'string',
description: 'Geolocation country code. Default "de".',
description: 'Country code. Default "de".',
},
hl: {
type: 'string',
description: 'Result language code. Default "de".',
description: 'Language code. Default "de".',
},
},
},

View File

@@ -63,9 +63,7 @@ async function serperScrape(url, apiKey, { signal } = {}) {
export function createUrlGetTool({ googleAuthId }) {
return defineTool({
name: 'url_get',
description:
'Fetch and scrape the contents of a web page using Serper Scrape. ' +
'Returns the raw scrape result as-is from Serper (text, markdown, metadata).',
description: 'Fetch and scrape web page content via Serper Scrape (text, markdown, metadata).',
strict: false,
parameters: {
type: 'object',
@@ -74,7 +72,7 @@ export function createUrlGetTool({ googleAuthId }) {
properties: {
url: {
type: 'string',
description: 'The URL or web address to fetch and scrape (e.g. "https://example.com" or "growheads.de").',
description: 'Target URL.',
},
},
},

View File

@@ -89,11 +89,7 @@ export function createWebSearchTool({ googleAuthId }) {
return defineTool({
name: 'web_search',
description:
'Search the web using Google Search via Serper.dev. ' +
'Returns raw search results as-is from Serper. ' +
'Supports filtering fresh content with timePeriod ("day", "week", "month", "year"). ' +
'Always use "day" or "week" when searching for news, recent events, or current status. ' +
'Each page returns 10 results.',
'Search Google via Serper.dev. Use timePeriod "day" or "week" for news/recent events. Returns 10 results per page.',
strict: false,
parameters: {
type: 'object',
@@ -102,27 +98,24 @@ export function createWebSearchTool({ googleAuthId }) {
properties: {
query: {
type: 'string',
description: 'Search query, e.g. "apple inc" or "vite vue3 release".',
description: 'Search query.',
},
timePeriod: {
type: 'string',
enum: ['day', 'week', 'month', 'year'],
description:
'Filter results by time period: "day" (past 24 hours), "week" (past week), ' +
'"month" (past month), or "year" (past year). ' +
'Always use "day" or "week" when searching for news, recent events, or current status.',
description: 'Time filter: "day", "week", "month", or "year".',
},
gl: {
type: 'string',
description: 'Geolocation country code. Default "de".',
description: 'Country code. Default "de".',
},
hl: {
type: 'string',
description: 'Result language code. Default "de".',
description: 'Language code. Default "de".',
},
page: {
type: 'number',
description: 'Page number of results. Default 1.',
description: 'Result page number (10/page). Default 1.',
},
},
},