@compilr-dev/sdk 0.18.1 → 0.18.2
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.
|
@@ -12,6 +12,18 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { PlatformToolsConfig } from '../context.js';
|
|
14
14
|
import type { ControlManifest } from '../../canvas/types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Guard the `html` arg against shapes that CANNOT render in the sandboxed
|
|
17
|
+
* canvas iframe (canvas-robustness-spec §3.3). Returns a corrective error
|
|
18
|
+
* string (which the agent sees as a tool error and retries on), or null if OK.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately conservative — only rejects clear cases so valid HTML/SVG is
|
|
21
|
+
* never blocked. In particular it does NOT trip on SVG namespaces
|
|
22
|
+
* (`xmlns="http://www.w3.org/2000/svg"`), inline `<script>`/`<style>`, or
|
|
23
|
+
* `<a href="http…">` anchors (those are all fine); it targets the loaders the
|
|
24
|
+
* CSP actually blocks.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateCanvasHtml(html: string): string | null;
|
|
15
27
|
export declare function createCanvasTools(config: PlatformToolsConfig): (import("@compilr-dev/agents").Tool<{
|
|
16
28
|
type: string;
|
|
17
29
|
title: string;
|
|
@@ -53,6 +53,48 @@ function countOccurrences(haystack, needle) {
|
|
|
53
53
|
}
|
|
54
54
|
return count;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Guard the `html` arg against shapes that CANNOT render in the sandboxed
|
|
58
|
+
* canvas iframe (canvas-robustness-spec §3.3). Returns a corrective error
|
|
59
|
+
* string (which the agent sees as a tool error and retries on), or null if OK.
|
|
60
|
+
*
|
|
61
|
+
* Deliberately conservative — only rejects clear cases so valid HTML/SVG is
|
|
62
|
+
* never blocked. In particular it does NOT trip on SVG namespaces
|
|
63
|
+
* (`xmlns="http://www.w3.org/2000/svg"`), inline `<script>`/`<style>`, or
|
|
64
|
+
* `<a href="http…">` anchors (those are all fine); it targets the loaders the
|
|
65
|
+
* CSP actually blocks.
|
|
66
|
+
*/
|
|
67
|
+
export function validateCanvasHtml(html) {
|
|
68
|
+
const src = html.trim();
|
|
69
|
+
if (!src)
|
|
70
|
+
return null; // emptiness is handled by the caller/schema
|
|
71
|
+
const hasTag = /<[a-z][\s\S]*>/i.test(src);
|
|
72
|
+
// 1. Mermaid source (diagram keyword at the very start, and no HTML tags).
|
|
73
|
+
const mermaidStart = /^(?:%%\{[^}]*\}%%\s*)?(?:graph\s|flowchart\s|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|mindmap|journey|pie\s|gitGraph|timeline|quadrantChart)/i;
|
|
74
|
+
if (!hasTag && mermaidStart.test(src)) {
|
|
75
|
+
return ('That looks like Mermaid, not HTML. The canvas renders raw HTML/SVG in a sandbox — ' +
|
|
76
|
+
'Mermaid does not run. Re-author as HTML, or draw the diagram as inline <svg> ' +
|
|
77
|
+
'(boxes with <rect>/<text>, connectors with <line>/<path>).');
|
|
78
|
+
}
|
|
79
|
+
// 2. Markdown source (a fenced block, or headings/lists) with no HTML tags.
|
|
80
|
+
if (!hasTag && (/^```/.test(src) || /^#{1,6}\s/.test(src) || /^[-*]\s/m.test(src))) {
|
|
81
|
+
return ('That looks like Markdown, not HTML. The canvas renders raw HTML/SVG — author it as ' +
|
|
82
|
+
'HTML (e.g. <h1>, <p>, <ul><li>), not Markdown.');
|
|
83
|
+
}
|
|
84
|
+
// 3. External / CDN resources. The sandbox CSP is script-src/style-src/img-src
|
|
85
|
+
// 'self' 'unsafe-inline' data: blob: — no network, no CDN — so these load
|
|
86
|
+
// attempts are silently blocked and the canvas renders blank. Only match a
|
|
87
|
+
// URL immediately after a loader attribute (src=/href=/@import/fetch) so SVG
|
|
88
|
+
// xmlns and anchor hrefs don't false-positive.
|
|
89
|
+
const externalLoader = /(?:<script\b[^>]*\bsrc|<link\b[^>]*\bhref|<img\b[^>]*\bsrc|@import\s+(?:url\()?)\s*=?\s*["'(]?\s*(?:https?:)?\/\//i;
|
|
90
|
+
const externalFetch = /\bfetch\s*\(\s*["'`](?:https?:)?\/\//i;
|
|
91
|
+
if (externalLoader.test(src) || externalFetch.test(src)) {
|
|
92
|
+
return ('The canvas sandbox blocks external scripts, styles, and images (no network / CDN). ' +
|
|
93
|
+
'Inline everything: use inline <svg> instead of a CDN library like Mermaid or Chart.js, ' +
|
|
94
|
+
'inline <style>/<script>, and data: URIs (or inline SVG) for images.');
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
56
98
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
57
99
|
export function createCanvasTools(config) {
|
|
58
100
|
const ctx = config.context;
|
|
@@ -64,7 +106,10 @@ export function createCanvasTools(config) {
|
|
|
64
106
|
// ---------------------------------------------------------------------------
|
|
65
107
|
const canvasWriteTool = defineTool({
|
|
66
108
|
name: 'canvas_write',
|
|
67
|
-
description: 'Create or update a visual canvas
|
|
109
|
+
description: 'Create or update a visual canvas — use this for ANY visual deliverable: a mockup, UI layout, ' +
|
|
110
|
+
'diagram, poster, infographic, slide deck, board, or a DESIGN PROPOSAL / REVAMP the user asks to "sketch", ' +
|
|
111
|
+
'"visualize", "propose a layout", or "redesign". (For a plain list of text options to pick from, that is ' +
|
|
112
|
+
'propose_alternatives, not this.) The canvas is authored as raw HTML/SVG (infographic, carousel, or board). ' +
|
|
68
113
|
'Optionally declare a controls manifest ("Tweaks") of live parameters the user can adjust: ' +
|
|
69
114
|
'each control is { type: slider|number|toggle|select|color|text, param, label, default, ...type config }. ' +
|
|
70
115
|
'Bind params in your HTML via CSS custom properties var(--param), [data-bind="param"] text, and ' +
|
|
@@ -84,8 +129,11 @@ export function createCanvasTools(config) {
|
|
|
84
129
|
title: { type: 'string', description: 'Canvas title (shown in the library and tab).' },
|
|
85
130
|
html: {
|
|
86
131
|
type: 'string',
|
|
87
|
-
description: 'The canvas content as HTML/SVG
|
|
88
|
-
'
|
|
132
|
+
description: 'The canvas content as RAW HTML/SVG — NOT Mermaid, NOT Markdown. The app renders it in a ' +
|
|
133
|
+
'sandboxed iframe with a strict CSP: NO external scripts/styles/images and NO network, so ' +
|
|
134
|
+
'inline everything (inline <style>/<script>, inline <svg> instead of a CDN chart/diagram ' +
|
|
135
|
+
'library, data: URIs for images). May include a <script> defining window.applyParams(values) ' +
|
|
136
|
+
'for live recompute.',
|
|
89
137
|
},
|
|
90
138
|
controls: {
|
|
91
139
|
type: 'object',
|
|
@@ -114,6 +162,12 @@ export function createCanvasTools(config) {
|
|
|
114
162
|
},
|
|
115
163
|
execute: async (input) => {
|
|
116
164
|
try {
|
|
165
|
+
// Reject content that can't render in the sandbox (Mermaid/Markdown/
|
|
166
|
+
// external-CDN) with a corrective message → the agent retries with
|
|
167
|
+
// inline HTML/SVG instead of persisting a blank canvas.
|
|
168
|
+
const htmlError = validateCanvasHtml(input.html);
|
|
169
|
+
if (htmlError)
|
|
170
|
+
return createErrorResult(htmlError);
|
|
117
171
|
if (input.controls) {
|
|
118
172
|
const check = validateControlManifest(input.controls);
|
|
119
173
|
if (!check.ok) {
|
|
@@ -25,10 +25,12 @@ import { defineTool } from '@compilr-dev/agents';
|
|
|
25
25
|
export function createProposeAlternativesTool(handler) {
|
|
26
26
|
return defineTool({
|
|
27
27
|
name: 'propose_alternatives',
|
|
28
|
-
description: 'Present 2-3 alternatives for the user to
|
|
29
|
-
"
|
|
30
|
-
'
|
|
31
|
-
'
|
|
28
|
+
description: 'Present 2-3 alternatives for the user to COMPARE and CHOOSE from — a DECISION tool for when ' +
|
|
29
|
+
"multiple valid approaches exist and the user's preference matters. Each alternative is a short " +
|
|
30
|
+
'text/code summary with pros and cons; the user picks one or gives feedback. ' +
|
|
31
|
+
'Do NOT use this to produce a visual deliverable — a mockup, UI layout, poster, diagram, or a ' +
|
|
32
|
+
'design PROPOSAL/REVAMP the user wants to SEE rendered. For anything visual, use the canvas ' +
|
|
33
|
+
'(canvas_write), even when the request says "propose" or "alternatives".',
|
|
32
34
|
inputSchema: {
|
|
33
35
|
type: 'object',
|
|
34
36
|
properties: {
|