@pinepaper.studio/mcp-server 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -25
- package/dist/agent/export-manager.d.ts.map +1 -1
- package/dist/agent/export-manager.js +24 -14
- package/dist/agent/export-manager.js.map +1 -1
- package/dist/cli.js +61 -66
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +66 -17
- package/dist/index.js.map +1 -1
- package/dist/metrics/types.d.ts +3 -1
- package/dist/metrics/types.d.ts.map +1 -1
- package/dist/prompts/index.d.ts +3 -12
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +5 -96
- package/dist/prompts/index.js.map +1 -1
- package/dist/tools/compact-descriptions.d.ts +1 -1
- package/dist/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +44 -13
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/definitions.d.ts +6 -4
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +154 -37
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +390 -5
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts +9 -0
- package/dist/tools/minimal-descriptions.d.ts.map +1 -0
- package/dist/tools/minimal-descriptions.js +158 -0
- package/dist/tools/minimal-descriptions.js.map +1 -0
- package/dist/tools/toolkits.d.ts +51 -0
- package/dist/tools/toolkits.d.ts.map +1 -0
- package/dist/tools/toolkits.js +336 -0
- package/dist/tools/toolkits.js.map +1 -0
- package/dist/types/code-generator.d.ts +1 -5
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +21 -192
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +42 -65
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +24 -48
- package/dist/types/schemas.js.map +1 -1
- package/manifest.json +30 -6
- package/package.json +1 -1
package/dist/tools/handlers.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Supports browser execution via Puppeteer.
|
|
7
7
|
*/
|
|
8
8
|
import { codeGenerator } from '../types/code-generator.js';
|
|
9
|
-
import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, AddRelationInputSchema, RemoveRelationInputSchema, QueryRelationsInputSchema, AnimateItemInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, GetItemsInputSchema, PlayTimelineInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, ExportTrainingDataInputSchema, ImportSVGInputSchema, AddFilterInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, CreateSceneInputSchema, GetPerformanceMetricsInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema,
|
|
9
|
+
import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, AddRelationInputSchema, RemoveRelationInputSchema, QueryRelationsInputSchema, AnimateItemInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, GetItemsInputSchema, PlayTimelineInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, ExportTrainingDataInputSchema, ImportSVGInputSchema, AddFilterInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, CreateSceneInputSchema, GetPerformanceMetricsInputSchema, DiagnosticReportInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema,
|
|
10
10
|
// Diagram schemas
|
|
11
11
|
CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
|
|
12
12
|
// Agent flow mode schemas
|
|
@@ -28,7 +28,9 @@ ApplyAnimatedMaskInputSchema, ApplyCustomMaskInputSchema, RemoveMaskInputSchema,
|
|
|
28
28
|
// Template schemas
|
|
29
29
|
ApplyTemplateInputSchema,
|
|
30
30
|
// Image import schemas
|
|
31
|
-
ImportImageInputSchema,
|
|
31
|
+
ImportImageInputSchema,
|
|
32
|
+
// Tool guide schema
|
|
33
|
+
ToolGuideInputSchema, ErrorCodes, } from '../types/schemas.js';
|
|
32
34
|
import { ZodError } from 'zod';
|
|
33
35
|
import { writeFile, mkdir } from 'node:fs/promises';
|
|
34
36
|
import { tmpdir } from 'node:os';
|
|
@@ -50,6 +52,43 @@ export function getScreenshotMode() {
|
|
|
50
52
|
return 'on_request';
|
|
51
53
|
}
|
|
52
54
|
// =============================================================================
|
|
55
|
+
// SMART DESIGN GUIDANCE — detect if user prompt has specific design direction
|
|
56
|
+
// =============================================================================
|
|
57
|
+
const COLOR_KEYWORDS = /(?:#[0-9a-f]{3,8}|rgb\(|hsl\(|\b(?:red|blue|green|purple|pink|gold|silver|orange|yellow|cyan|magenta|indigo|teal|coral|crimson|navy|maroon|turquoise|violet|amber|emerald|scarlet)\b)/i;
|
|
58
|
+
const STYLE_KEYWORDS = /\b(?:gradient|bokeh|geometric|circuit|wave|sunset|sunrise|minimal|modern|retro|vintage|elegant|playful|abstract|organic|neon|pastel|watercolor|grunge|futuristic|cosmic|dreamy|glossy)\b/i;
|
|
59
|
+
const LAYOUT_KEYWORDS = /\b(?:centered|left-aligned|right-aligned|grid|stacked|scattered|radial|circular|diagonal|symmetrical|horizontal|vertical)\b/i;
|
|
60
|
+
const ANIMATION_KEYWORDS = /\b(?:orbit|follow|pulse|bounce|fade|rotate|wobble|slide|wipe|reveal|parallax|wave|morph|float|glow|sparkle|burst|spiral)\b/i;
|
|
61
|
+
/**
|
|
62
|
+
* Detect if a prompt has enough design specificity to skip guidance injection.
|
|
63
|
+
* Returns true if the prompt is design-specific (skip guidance).
|
|
64
|
+
* Returns false if vague (inject guidance).
|
|
65
|
+
*/
|
|
66
|
+
function detectDesignSpecificity(prompt) {
|
|
67
|
+
if (!prompt || prompt.trim().length === 0)
|
|
68
|
+
return false;
|
|
69
|
+
let specificityScore = 0;
|
|
70
|
+
// Color mentions: strong signal of specificity
|
|
71
|
+
if (COLOR_KEYWORDS.test(prompt))
|
|
72
|
+
specificityScore += 2;
|
|
73
|
+
// Style/mood keywords
|
|
74
|
+
if (STYLE_KEYWORDS.test(prompt))
|
|
75
|
+
specificityScore += 2;
|
|
76
|
+
// Layout direction
|
|
77
|
+
if (LAYOUT_KEYWORDS.test(prompt))
|
|
78
|
+
specificityScore += 1;
|
|
79
|
+
// Animation specifics
|
|
80
|
+
if (ANIMATION_KEYWORDS.test(prompt))
|
|
81
|
+
specificityScore += 1;
|
|
82
|
+
// Word count: detailed prompts tend to be longer
|
|
83
|
+
const wordCount = prompt.trim().split(/\s+/).length;
|
|
84
|
+
if (wordCount >= 20)
|
|
85
|
+
specificityScore += 1;
|
|
86
|
+
if (wordCount >= 35)
|
|
87
|
+
specificityScore += 1;
|
|
88
|
+
// Threshold: 3+ = specific enough, skip guidance
|
|
89
|
+
return specificityScore >= 3;
|
|
90
|
+
}
|
|
91
|
+
// =============================================================================
|
|
53
92
|
// EXPORT FILE SAVE HELPERS
|
|
54
93
|
// =============================================================================
|
|
55
94
|
function getExportDir() {
|
|
@@ -412,10 +451,43 @@ async function executeToolWithTracking(toolName, args, options, validator, codeG
|
|
|
412
451
|
// =============================================================================
|
|
413
452
|
// TOOL HANDLERS
|
|
414
453
|
// =============================================================================
|
|
454
|
+
/**
|
|
455
|
+
* Measure the approximate byte size of a CallToolResult payload.
|
|
456
|
+
* Text content is measured directly; image content estimates base64 overhead.
|
|
457
|
+
*/
|
|
458
|
+
function measureResponseBytes(result) {
|
|
459
|
+
let bytes = 0;
|
|
460
|
+
if (result.content) {
|
|
461
|
+
for (const item of result.content) {
|
|
462
|
+
if (item.type === 'text') {
|
|
463
|
+
bytes += Buffer.byteLength(item.text, 'utf-8');
|
|
464
|
+
}
|
|
465
|
+
else if (item.type === 'image') {
|
|
466
|
+
// Base64 image data
|
|
467
|
+
bytes += (item.data?.length ?? 0);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return bytes;
|
|
472
|
+
}
|
|
415
473
|
export async function handleToolCall(toolName, args, options = {}) {
|
|
416
|
-
const { i18n } = options;
|
|
417
474
|
const tracker = getPerformanceTracker();
|
|
418
475
|
const baseTimerId = `${toolName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
476
|
+
const result = await handleToolCallInner(toolName, args, options, tracker, baseTimerId);
|
|
477
|
+
// Record response payload size for token estimation
|
|
478
|
+
const responseBytes = measureResponseBytes(result);
|
|
479
|
+
tracker.recordMetric({
|
|
480
|
+
toolName,
|
|
481
|
+
phase: 'response_size',
|
|
482
|
+
duration: 0,
|
|
483
|
+
timestamp: Date.now(),
|
|
484
|
+
success: !result.isError,
|
|
485
|
+
responseBytes,
|
|
486
|
+
});
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
async function handleToolCallInner(toolName, args, options, tracker, baseTimerId) {
|
|
490
|
+
const { i18n } = options;
|
|
419
491
|
try {
|
|
420
492
|
switch (toolName) {
|
|
421
493
|
// -----------------------------------------------------------------------
|
|
@@ -1094,6 +1166,286 @@ All PinePaper tools will work in **code-only mode**:
|
|
|
1094
1166
|
};
|
|
1095
1167
|
}
|
|
1096
1168
|
// -----------------------------------------------------------------------
|
|
1169
|
+
// DIAGNOSTIC REPORT
|
|
1170
|
+
// -----------------------------------------------------------------------
|
|
1171
|
+
case 'pinepaper_diagnostic_report': {
|
|
1172
|
+
const input = DiagnosticReportInputSchema.parse(args);
|
|
1173
|
+
const includeMetrics = input.includeMetrics !== false;
|
|
1174
|
+
const includeCanvas = input.includeCanvas !== false;
|
|
1175
|
+
const metricsLimit = input.metricsLimit ?? 100;
|
|
1176
|
+
// --- Server info ---
|
|
1177
|
+
const { detectToolkitFromEnvironment: detectToolkit, detectVerbosityFromEnvironment: detectVerbosity } = await import('./toolkits.js');
|
|
1178
|
+
const serverSection = {
|
|
1179
|
+
version: '1.5.1',
|
|
1180
|
+
toolkit: detectToolkit(),
|
|
1181
|
+
verbosity: detectVerbosity(),
|
|
1182
|
+
locale: process.env.PINEPAPER_LOCALE || 'en',
|
|
1183
|
+
executionMode: options.executionMode || getExecutionMode(),
|
|
1184
|
+
screenshotMode: options.screenshotMode || getScreenshotMode(),
|
|
1185
|
+
};
|
|
1186
|
+
// --- Browser state ---
|
|
1187
|
+
let browserSection = { connected: false };
|
|
1188
|
+
try {
|
|
1189
|
+
const ctrl = options.browserController || getBrowserController();
|
|
1190
|
+
browserSection = {
|
|
1191
|
+
connected: ctrl.connected,
|
|
1192
|
+
agentMode: ctrl.agentMode,
|
|
1193
|
+
studioUrl: ctrl.studioUrl,
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
catch { /* browser not initialized */ }
|
|
1197
|
+
// --- Session state ---
|
|
1198
|
+
let sessionSection = {};
|
|
1199
|
+
try {
|
|
1200
|
+
const session = getSessionManager();
|
|
1201
|
+
sessionSection = session.getStats();
|
|
1202
|
+
}
|
|
1203
|
+
catch { /* session not initialized */ }
|
|
1204
|
+
// --- Canvas state ---
|
|
1205
|
+
let canvasSection = null;
|
|
1206
|
+
if (includeCanvas) {
|
|
1207
|
+
try {
|
|
1208
|
+
const ctrl = options.browserController || getBrowserController();
|
|
1209
|
+
if (ctrl.connected) {
|
|
1210
|
+
canvasSection = await captureCanvasState(ctrl);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
catch { /* canvas capture failed */ }
|
|
1214
|
+
}
|
|
1215
|
+
// --- Performance metrics ---
|
|
1216
|
+
let metricsSection = null;
|
|
1217
|
+
if (includeMetrics) {
|
|
1218
|
+
try {
|
|
1219
|
+
const perfTracker = getPerformanceTracker();
|
|
1220
|
+
const recentMetrics = perfTracker.exportMetrics('json', { limit: metricsLimit });
|
|
1221
|
+
const stats = perfTracker.getStats();
|
|
1222
|
+
metricsSection = { entries: recentMetrics, stats };
|
|
1223
|
+
}
|
|
1224
|
+
catch { /* metrics not available */ }
|
|
1225
|
+
}
|
|
1226
|
+
// --- Tool definitions overhead (fixed cost per API turn) ---
|
|
1227
|
+
let toolDefsSection = null;
|
|
1228
|
+
try {
|
|
1229
|
+
const { PINEPAPER_TOOLS, getToolsForVerbosity } = await import('./definitions.js');
|
|
1230
|
+
const { getToolsForToolkit } = await import('./toolkits.js');
|
|
1231
|
+
const toolkit = detectToolkit();
|
|
1232
|
+
const verbosity = detectVerbosity();
|
|
1233
|
+
// Measure what actually gets sent to the LLM
|
|
1234
|
+
const activeTools = getToolsForToolkit(getToolsForVerbosity(verbosity), toolkit);
|
|
1235
|
+
const activeJson = JSON.stringify(activeTools);
|
|
1236
|
+
const activeBytes = Buffer.byteLength(activeJson, 'utf-8');
|
|
1237
|
+
const activeTokens = Math.ceil(activeBytes / 4);
|
|
1238
|
+
// Also measure full set for comparison
|
|
1239
|
+
const fullJson = JSON.stringify(PINEPAPER_TOOLS);
|
|
1240
|
+
const fullBytes = Buffer.byteLength(fullJson, 'utf-8');
|
|
1241
|
+
const fullTokens = Math.ceil(fullBytes / 4);
|
|
1242
|
+
// Measure other profiles for comparison recommendations
|
|
1243
|
+
const agentCompactTools = getToolsForToolkit(getToolsForVerbosity('compact'), 'agent');
|
|
1244
|
+
const agentCompactTokens = Math.ceil(Buffer.byteLength(JSON.stringify(agentCompactTools), 'utf-8') / 4);
|
|
1245
|
+
const agentMinimalTools = getToolsForToolkit(getToolsForVerbosity('minimal'), 'agent');
|
|
1246
|
+
const agentMinimalTokens = Math.ceil(Buffer.byteLength(JSON.stringify(agentMinimalTools), 'utf-8') / 4);
|
|
1247
|
+
// Per-tool breakdown of active set (top 10 heaviest)
|
|
1248
|
+
const perToolDef = activeTools.map((t) => ({
|
|
1249
|
+
tool: t.name,
|
|
1250
|
+
bytes: Buffer.byteLength(JSON.stringify(t), 'utf-8'),
|
|
1251
|
+
descBytes: Buffer.byteLength(t.description || '', 'utf-8'),
|
|
1252
|
+
})).sort((a, b) => b.bytes - a.bytes);
|
|
1253
|
+
const totalDescBytes = perToolDef.reduce((s, t) => s + t.descBytes, 0);
|
|
1254
|
+
toolDefsSection = {
|
|
1255
|
+
toolkit,
|
|
1256
|
+
verbosity,
|
|
1257
|
+
activeToolCount: activeTools.length,
|
|
1258
|
+
activeBytes,
|
|
1259
|
+
activeTokens,
|
|
1260
|
+
fullToolCount: PINEPAPER_TOOLS.length,
|
|
1261
|
+
fullBytes,
|
|
1262
|
+
fullTokens,
|
|
1263
|
+
descriptionBytes: totalDescBytes,
|
|
1264
|
+
descriptionPct: ((totalDescBytes / activeBytes) * 100).toFixed(1) + '%',
|
|
1265
|
+
top10: perToolDef.slice(0, 10).map((t) => ({
|
|
1266
|
+
tool: t.tool,
|
|
1267
|
+
bytes: t.bytes,
|
|
1268
|
+
tokens: Math.ceil(t.bytes / 4),
|
|
1269
|
+
})),
|
|
1270
|
+
profiles: {
|
|
1271
|
+
'full+verbose': fullTokens,
|
|
1272
|
+
'agent+compact': agentCompactTokens,
|
|
1273
|
+
'agent+minimal': agentMinimalTokens,
|
|
1274
|
+
},
|
|
1275
|
+
recommendations: [],
|
|
1276
|
+
};
|
|
1277
|
+
// Add actionable recommendations
|
|
1278
|
+
const recs = toolDefsSection.recommendations;
|
|
1279
|
+
if (toolkit === 'full') {
|
|
1280
|
+
recs.push(`Switch to PINEPAPER_TOOLKIT=agent to reduce to ~${agentCompactTokens.toLocaleString()} tokens/turn (agent+compact)`);
|
|
1281
|
+
}
|
|
1282
|
+
if (verbosity === 'verbose') {
|
|
1283
|
+
recs.push(`Set PINEPAPER_VERBOSITY=compact to reduce tool definitions overhead`);
|
|
1284
|
+
}
|
|
1285
|
+
if (verbosity !== 'minimal') {
|
|
1286
|
+
recs.push(`Set PINEPAPER_VERBOSITY=minimal for maximum savings (~${agentMinimalTokens.toLocaleString()} tokens with agent toolkit) — use pinepaper_tool_guide for on-demand docs`);
|
|
1287
|
+
}
|
|
1288
|
+
if (activeTokens > 20000) {
|
|
1289
|
+
recs.push(`Tool definitions alone consume ~${activeTokens.toLocaleString()} tokens per API turn — this is a major context budget item`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
catch { /* definitions not available */ }
|
|
1293
|
+
// --- Token usage estimate (response payloads) ---
|
|
1294
|
+
let tokenSection = null;
|
|
1295
|
+
try {
|
|
1296
|
+
const perfTracker = getPerformanceTracker();
|
|
1297
|
+
const responseSizeMetrics = perfTracker.getMetrics({ phase: 'response_size' });
|
|
1298
|
+
// Aggregate per-tool response sizes
|
|
1299
|
+
const perTool = new Map();
|
|
1300
|
+
let totalResponseBytes = 0;
|
|
1301
|
+
for (const m of responseSizeMetrics) {
|
|
1302
|
+
const bytes = m.responseBytes ?? 0;
|
|
1303
|
+
totalResponseBytes += bytes;
|
|
1304
|
+
const entry = perTool.get(m.toolName) ?? { calls: 0, totalBytes: 0 };
|
|
1305
|
+
entry.calls++;
|
|
1306
|
+
entry.totalBytes += bytes;
|
|
1307
|
+
perTool.set(m.toolName, entry);
|
|
1308
|
+
}
|
|
1309
|
+
// ~4 chars per token is a common estimate for English text
|
|
1310
|
+
const estimatedTokens = Math.ceil(totalResponseBytes / 4);
|
|
1311
|
+
// Sort by totalBytes descending
|
|
1312
|
+
const perToolSorted = [...perTool.entries()]
|
|
1313
|
+
.sort((a, b) => b[1].totalBytes - a[1].totalBytes)
|
|
1314
|
+
.map(([tool, data]) => ({
|
|
1315
|
+
tool,
|
|
1316
|
+
calls: data.calls,
|
|
1317
|
+
totalBytes: data.totalBytes,
|
|
1318
|
+
estimatedTokens: Math.ceil(data.totalBytes / 4),
|
|
1319
|
+
avgBytesPerCall: Math.round(data.totalBytes / data.calls),
|
|
1320
|
+
}));
|
|
1321
|
+
tokenSection = {
|
|
1322
|
+
totalToolCalls: responseSizeMetrics.length,
|
|
1323
|
+
totalResponseBytes,
|
|
1324
|
+
estimatedResponseTokens: estimatedTokens,
|
|
1325
|
+
perTool: perToolSorted,
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
catch { /* metrics not available */ }
|
|
1329
|
+
// --- Assemble report ---
|
|
1330
|
+
const report = {
|
|
1331
|
+
timestamp: new Date().toISOString(),
|
|
1332
|
+
server: serverSection,
|
|
1333
|
+
browser: browserSection,
|
|
1334
|
+
session: sessionSection,
|
|
1335
|
+
canvas: canvasSection,
|
|
1336
|
+
metrics: metricsSection,
|
|
1337
|
+
toolDefinitions: toolDefsSection,
|
|
1338
|
+
tokenUsage: tokenSection,
|
|
1339
|
+
};
|
|
1340
|
+
// --- Save to disk ---
|
|
1341
|
+
const exportDir = getExportDir();
|
|
1342
|
+
await mkdir(exportDir, { recursive: true });
|
|
1343
|
+
const fileName = `pinepaper_diagnostic_${Date.now()}.json`;
|
|
1344
|
+
const filePath = join(exportDir, fileName);
|
|
1345
|
+
const jsonContent = JSON.stringify(report, null, 2);
|
|
1346
|
+
await writeFile(filePath, jsonContent, 'utf-8');
|
|
1347
|
+
// --- Build summary (token budget first — it's what matters most) ---
|
|
1348
|
+
const lines = [
|
|
1349
|
+
`Diagnostic report saved to: ${filePath}`,
|
|
1350
|
+
``,
|
|
1351
|
+
];
|
|
1352
|
+
// Token budget section — prominent at top
|
|
1353
|
+
if (toolDefsSection) {
|
|
1354
|
+
const td = toolDefsSection;
|
|
1355
|
+
lines.push(`TOKEN BUDGET`);
|
|
1356
|
+
lines.push(` Tool definitions: ~${td.activeTokens.toLocaleString()} tokens/turn (${td.activeToolCount} tools, ${(td.activeBytes / 1024).toFixed(0)} KB)`);
|
|
1357
|
+
lines.push(` Descriptions are ${td.descriptionPct} of that overhead`);
|
|
1358
|
+
lines.push(` Config: toolkit=${td.toolkit}, verbosity=${td.verbosity}`);
|
|
1359
|
+
if (tokenSection) {
|
|
1360
|
+
const tk = tokenSection;
|
|
1361
|
+
lines.push(` Response payloads: ~${tk.estimatedResponseTokens.toLocaleString()} tokens across ${tk.totalToolCalls} tool calls (${(tk.totalResponseBytes / 1024).toFixed(1)} KB)`);
|
|
1362
|
+
const totalPerTurn = td.activeTokens + Math.ceil(tk.estimatedResponseTokens / Math.max(tk.totalToolCalls, 1));
|
|
1363
|
+
lines.push(` Estimated per-turn cost: ~${totalPerTurn.toLocaleString()} tokens (definitions + avg response)`);
|
|
1364
|
+
}
|
|
1365
|
+
// Top 3 heaviest tool definitions
|
|
1366
|
+
const topDefs = td.top10.slice(0, 3);
|
|
1367
|
+
if (topDefs.length > 0) {
|
|
1368
|
+
lines.push(` Heaviest definitions: ${topDefs.map(t => `${t.tool} (~${t.tokens.toLocaleString()}t)`).join(', ')}`);
|
|
1369
|
+
}
|
|
1370
|
+
// Top 3 heaviest response consumers
|
|
1371
|
+
if (tokenSection) {
|
|
1372
|
+
const tk = tokenSection;
|
|
1373
|
+
const topResp = tk.perTool.slice(0, 3);
|
|
1374
|
+
if (topResp.length > 0) {
|
|
1375
|
+
lines.push(` Heaviest responses: ${topResp.map(t => `${t.tool} (${t.calls}x, ~${t.estimatedTokens.toLocaleString()}t)`).join(', ')}`);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
if (td.recommendations.length > 0) {
|
|
1379
|
+
lines.push(``);
|
|
1380
|
+
lines.push(`RECOMMENDATIONS`);
|
|
1381
|
+
for (const rec of td.recommendations) {
|
|
1382
|
+
lines.push(` - ${rec}`);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
lines.push(``);
|
|
1386
|
+
}
|
|
1387
|
+
// Brief state summary
|
|
1388
|
+
lines.push(`Server: v${serverSection.version} | toolkit=${serverSection.toolkit} | mode=${serverSection.executionMode}`);
|
|
1389
|
+
lines.push(`Browser: ${browserSection.connected ? 'connected' : 'disconnected'}`);
|
|
1390
|
+
lines.push(`Session: ${sessionSection.activeJob ? `active job (${sessionSection.currentJobId})` : 'no active job'} | ${sessionSection.completedJobs ?? 0} completed | ${sessionSection.totalItemsCreated ?? 0} items total`);
|
|
1391
|
+
if (canvasSection) {
|
|
1392
|
+
const cs = canvasSection;
|
|
1393
|
+
lines.push(`Canvas: ${cs.itemCount ?? 0} items | ${JSON.stringify(cs.canvasSize ?? {})}`);
|
|
1394
|
+
}
|
|
1395
|
+
return {
|
|
1396
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
// -----------------------------------------------------------------------
|
|
1400
|
+
// TOOL GUIDE (on-demand documentation)
|
|
1401
|
+
// -----------------------------------------------------------------------
|
|
1402
|
+
case 'pinepaper_tool_guide': {
|
|
1403
|
+
const input = ToolGuideInputSchema.parse(args);
|
|
1404
|
+
const { PINEPAPER_TOOLS, AI_AGENT_GUIDE } = await import('./definitions.js');
|
|
1405
|
+
const { TOOL_TAGS } = await import('./toolkits.js');
|
|
1406
|
+
// No args → full AI Agent Guide
|
|
1407
|
+
if (!input.tool && !input.category) {
|
|
1408
|
+
return {
|
|
1409
|
+
content: [{ type: 'text', text: AI_AGENT_GUIDE }],
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
// Specific tool → full verbose description
|
|
1413
|
+
if (input.tool) {
|
|
1414
|
+
const tool = PINEPAPER_TOOLS.find((t) => t.name === input.tool);
|
|
1415
|
+
if (!tool) {
|
|
1416
|
+
return {
|
|
1417
|
+
content: [{ type: 'text', text: `Unknown tool: ${input.tool}. Use pinepaper_tool_guide with no args to see the AI Agent Guide.` }],
|
|
1418
|
+
isError: true,
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
return {
|
|
1422
|
+
content: [{ type: 'text', text: `# ${tool.name}\n\n${tool.description}` }],
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
// Category → all tools in that tag with descriptions
|
|
1426
|
+
if (input.category) {
|
|
1427
|
+
const toolNames = TOOL_TAGS[input.category];
|
|
1428
|
+
if (!toolNames) {
|
|
1429
|
+
const available = Object.keys(TOOL_TAGS).join(', ');
|
|
1430
|
+
return {
|
|
1431
|
+
content: [{ type: 'text', text: `Unknown category: ${input.category}. Available: ${available}` }],
|
|
1432
|
+
isError: true,
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
const lines = [`# Category: ${input.category}\n`];
|
|
1436
|
+
for (const name of toolNames) {
|
|
1437
|
+
const tool = PINEPAPER_TOOLS.find((t) => t.name === name);
|
|
1438
|
+
if (tool) {
|
|
1439
|
+
lines.push(`## ${tool.name}\n${tool.description}\n`);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return {
|
|
1443
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
return { content: [{ type: 'text', text: AI_AGENT_GUIDE }] };
|
|
1447
|
+
}
|
|
1448
|
+
// -----------------------------------------------------------------------
|
|
1097
1449
|
// ASSET TOOLS
|
|
1098
1450
|
// -----------------------------------------------------------------------
|
|
1099
1451
|
case 'pinepaper_search_assets': {
|
|
@@ -1306,8 +1658,37 @@ All PinePaper tools will work in **code-only mode**:
|
|
|
1306
1658
|
}
|
|
1307
1659
|
}
|
|
1308
1660
|
const code = codeGenerator.generateAgentStartJob(input);
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1661
|
+
const descriptionText = `Started agent job${input.name ? ` "${input.name}"` : ''} with ${input.screenshotPolicy || 'on_complete'} screenshot policy`;
|
|
1662
|
+
const result = await executeOrGenerate(code, descriptionText, options, 'pinepaper_agent_start_job');
|
|
1663
|
+
// --- Smart design guidance injection ---
|
|
1664
|
+
if (result.content && result.content.length > 0 && result.content[0].type === 'text') {
|
|
1665
|
+
const prompt = (input.description || input.name || '').toLowerCase();
|
|
1666
|
+
const isDesignSpecific = detectDesignSpecificity(prompt);
|
|
1667
|
+
// Always include workflow hint
|
|
1668
|
+
let guidance = `\n\nNEXT: Call pinepaper_agent_batch_execute with ALL operations in one call:` +
|
|
1669
|
+
`\n 1. set_canvas_size / set_background / execute_generator (canvas setup)` +
|
|
1670
|
+
`\n 2. create (items — text, shapes, etc.)` +
|
|
1671
|
+
`\n 3. animate / keyframe_animate / relation (REQUIRED for animation — add for EACH item)` +
|
|
1672
|
+
`\n 4. play_timeline (REQUIRED — starts playback)` +
|
|
1673
|
+
`\nThen: pinepaper_agent_end_job (returns screenshot for validation)`;
|
|
1674
|
+
// Inject design inspiration when the prompt is vague
|
|
1675
|
+
if (!isDesignSpecific) {
|
|
1676
|
+
guidance += `\n\n─── DESIGN INSPIRATION (your prompt is open-ended — use these for creative direction) ───` +
|
|
1677
|
+
`\nBACKGROUND: Use execute_generator for rich visuals:` +
|
|
1678
|
+
`\n Dreamy/soft: drawBokeh, drawGradientMesh, drawOrganicFlow` +
|
|
1679
|
+
`\n Energetic: drawSunburst, drawSunsetScene, drawGeometricAbstract` +
|
|
1680
|
+
`\n Tech/modern: drawCircuit, drawGrid, drawWindField` +
|
|
1681
|
+
`\n Calm/organic: drawWaves, drawFluidFlow, drawNoiseTexture` +
|
|
1682
|
+
`\nCOLORS: dark bg (#0f172a, #1a1a2e) + bright accents (#f472b6 pink, #818cf8 purple, #fbbf24 gold, #34d399 green)` +
|
|
1683
|
+
`\nCOMPOSITION: generator bg → large decorative shapes (low opacity 0.3-0.5) → main text → small accents on top` +
|
|
1684
|
+
`\nANIMATION: Use "relation" ops for dynamic motion:` +
|
|
1685
|
+
`\n orbits (items circle around center), follows (items trail each other), wave_through (ripple effect)` +
|
|
1686
|
+
`\n Plus: keyframe_animate for timed reveals (fade in, scale up), animate for loops (pulse, rotate, bounce)` +
|
|
1687
|
+
`\n5s TIMING: bg 0s → shapes fade-in 0.5-1.5s → main text 2-3s → accents 3-4s → play_timeline`;
|
|
1688
|
+
}
|
|
1689
|
+
result.content[0].text += guidance;
|
|
1690
|
+
}
|
|
1691
|
+
return result;
|
|
1311
1692
|
}
|
|
1312
1693
|
case 'pinepaper_agent_end_job': {
|
|
1313
1694
|
const input = AgentEndJobInputSchema.parse(args);
|
|
@@ -1762,6 +2143,7 @@ All PinePaper tools will work in **code-only mode**:
|
|
|
1762
2143
|
'pinepaper_browser_screenshot',
|
|
1763
2144
|
'pinepaper_browser_status',
|
|
1764
2145
|
'pinepaper_get_performance_metrics',
|
|
2146
|
+
'pinepaper_diagnostic_report',
|
|
1765
2147
|
'pinepaper_search_assets',
|
|
1766
2148
|
'pinepaper_import_asset',
|
|
1767
2149
|
'pinepaper_p5_draw',
|
|
@@ -1846,6 +2228,9 @@ All PinePaper tools will work in **code-only mode**:
|
|
|
1846
2228
|
'pinepaper_import_image',
|
|
1847
2229
|
// Paper.js direct access tools
|
|
1848
2230
|
'pinepaper_register_item',
|
|
2231
|
+
// On-demand guide & runtime config
|
|
2232
|
+
'pinepaper_tool_guide',
|
|
2233
|
+
'pinepaper_set_toolkit',
|
|
1849
2234
|
],
|
|
1850
2235
|
});
|
|
1851
2236
|
}
|