@compilr-dev/sdk 0.18.0 → 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,13 +106,18 @@ 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 ' +
|
|
71
116
|
'[data-show="param"] visibility; for computed updates define window.applyParams(values) in a <script>. ' +
|
|
72
117
|
'Omit canvas_id to create. To EDIT an existing canvas, prefer canvas_edit (str_replace) — it avoids ' +
|
|
73
|
-
're-sending the whole document; only use canvas_write with canvas_id for a full intentional replace.'
|
|
118
|
+
're-sending the whole document; only use canvas_write with canvas_id for a full intentional replace. ' +
|
|
119
|
+
'Before creating a NEW canvas, gather the essentials from the user (purpose, type, key content, style) ' +
|
|
120
|
+
'with ask_user unless they already specified them — authoring on guessed requirements wastes a full pass.',
|
|
74
121
|
inputSchema: {
|
|
75
122
|
type: 'object',
|
|
76
123
|
properties: {
|
|
@@ -82,8 +129,11 @@ export function createCanvasTools(config) {
|
|
|
82
129
|
title: { type: 'string', description: 'Canvas title (shown in the library and tab).' },
|
|
83
130
|
html: {
|
|
84
131
|
type: 'string',
|
|
85
|
-
description: 'The canvas content as HTML/SVG
|
|
86
|
-
'
|
|
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.',
|
|
87
137
|
},
|
|
88
138
|
controls: {
|
|
89
139
|
type: 'object',
|
|
@@ -112,6 +162,12 @@ export function createCanvasTools(config) {
|
|
|
112
162
|
},
|
|
113
163
|
execute: async (input) => {
|
|
114
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);
|
|
115
171
|
if (input.controls) {
|
|
116
172
|
const check = validateControlManifest(input.controls);
|
|
117
173
|
if (!check.ok) {
|
|
@@ -35,7 +35,13 @@ export const canvasSkill = defineSkill({
|
|
|
35
35
|
|
|
36
36
|
## STEPS
|
|
37
37
|
|
|
38
|
-
1. **
|
|
38
|
+
1. **Collect the essentials from the user FIRST — this is the standard approach.** A canvas is an expensive authoring pass; building it on guessed requirements wastes that pass and lands wide of what the user wanted. So before you author, use **\`ask_user\`** (batch several questions into ONE call) to gather what the canvas needs:
|
|
39
|
+
- **Purpose & audience** — what is it for, who reads it?
|
|
40
|
+
- **Canvas type** — infographic, carousel, or board? (Offer the choice unless obvious.)
|
|
41
|
+
- **Key content / data** — the actual points, numbers, sections, or slides to include. Don't invent data.
|
|
42
|
+
- **Style / brand** — theme-matched (default) or a specific palette/brand?
|
|
43
|
+
For open design directions (which layout, which visual approach), present concrete options with **\`propose_alternatives\`** instead of guessing.
|
|
44
|
+
**Skip questions the user already answered**, and if they gave full detail or explicitly said "just make it / surprise me / your call", go straight to authoring. Keep it to ONE focused round — a short question batch, never a long interview — then build.
|
|
39
45
|
|
|
40
46
|
2. **Build the canvas in small steps — never one giant tool call.** A canvas is HTML/SVG you emit as tool arguments; a single very large \`canvas_write\` can overrun the output limit, get cut off mid-arguments, and fail. So author it incrementally, and emit each tool call immediately with NO prose preamble (narration competes with the HTML for the same output budget):
|
|
41
47
|
- **2a. First \`canvas_write\`** (type, title, html) = a COMPACT skeleton: the \`<style>\` block, the overall layout, and just the first section or heading. This is the ONLY way to create a canvas — describing it in chat does nothing.
|
|
@@ -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: {
|