@compilr-dev/sdk 0.18.2 → 0.18.3
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/dist/capabilities/packs.js +9 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.js +1 -1
- package/dist/models/model-registry.d.ts +14 -0
- package/dist/models/model-registry.js +39 -0
- package/dist/platform/tools/canvas-tools.d.ts +7 -1
- package/dist/platform/tools/canvas-tools.js +188 -1
- package/dist/skills/canvas-skills.js +3 -1
- package/dist/team/tool-config.js +8 -1
- package/package.json +5 -1
|
@@ -236,10 +236,17 @@ export const CAPABILITY_PACKS = {
|
|
|
236
236
|
canvas: {
|
|
237
237
|
id: 'canvas',
|
|
238
238
|
label: 'Visual Canvas',
|
|
239
|
-
tools: [
|
|
239
|
+
tools: [
|
|
240
|
+
'canvas_write',
|
|
241
|
+
'canvas_edit',
|
|
242
|
+
'canvas_list',
|
|
243
|
+
'canvas_get',
|
|
244
|
+
'canvas_delete',
|
|
245
|
+
'canvas_validate',
|
|
246
|
+
],
|
|
240
247
|
readOnly: false,
|
|
241
248
|
promptModules: ['platform-tool-hints'],
|
|
242
|
-
promptSnippet:
|
|
249
|
+
promptSnippet: "Visual canvases (infographic/carousel/board) rendered from sandboxed HTML/SVG. Create/replace with canvas_write (raw HTML only — host injects the CSP; inline <style>/<script> only, no network/remote assets; optional Tweaks controls manifest). EDIT via canvas_get (outline=true or startLine/maxLines) then canvas_edit (str_replace/append/prepend) — never re-send the whole document. After authoring, canvas_validate to catch won't-render content, off-canvas board nodes, and un-themed colors.",
|
|
243
250
|
estimatedPromptTokens: 150,
|
|
244
251
|
estimatedToolTokens: 1500,
|
|
245
252
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export type { Preset } from './presets/index.js';
|
|
|
45
45
|
export type { AnyTool } from './presets/types.js';
|
|
46
46
|
export { resolveProvider, detectProviderFromEnv, createProviderFromType } from './provider.js';
|
|
47
47
|
export { DEFAULT_MODELS, getContextWindow, DEFAULT_CONTEXT_WINDOW } from './models.js';
|
|
48
|
-
export { type ModelTier, type TierInfo, type ProviderModelMap, MODEL_TIERS, TIER_INFO, isValidTier, type ThinkingFormat, type ModelStatus, type ModelInfo, MODEL_REGISTRY, getModelsForProvider, getModelsSortedForDisplay, getModelInfo, isKnownModel, isModelSupported, getThinkingFormat, getStatusIndicator, getStatusLabel, getDefaultModelForTier, areThinkingFormatsCompatible, shouldClearHistoryOnModelChange, getModelContextWindow, getModelDisplayName, getModelDescription, getModelForTier, getTierMappings, getTierDisplayName, getShortModelName, getDefaultTierMappings, type ProviderMetadata, type OthersProviderModel, PROVIDER_METADATA, TOGETHER_MODELS, GROQ_MODELS, FIREWORKS_MODELS, PERPLEXITY_MODELS, OPENROUTER_MODELS, getModelsForOthersProvider, getOthersProviders, getProviderMetadata, isOthersProvider, } from './models/index.js';
|
|
48
|
+
export { type ModelTier, type TierInfo, type ProviderModelMap, MODEL_TIERS, TIER_INFO, isValidTier, type ThinkingFormat, type ModelStatus, type ModelInfo, MODEL_REGISTRY, getModelsForProvider, getModelsSortedForDisplay, getModelInfo, isKnownModel, isModelSupported, modelSupportsImages, getThinkingFormat, getStatusIndicator, getStatusLabel, getDefaultModelForTier, areThinkingFormatsCompatible, shouldClearHistoryOnModelChange, getModelContextWindow, getModelDisplayName, getModelDescription, getModelForTier, getTierMappings, getTierDisplayName, getShortModelName, getDefaultTierMappings, type ProviderMetadata, type OthersProviderModel, PROVIDER_METADATA, TOGETHER_MODELS, GROQ_MODELS, FIREWORKS_MODELS, PERPLEXITY_MODELS, OPENROUTER_MODELS, getModelsForOthersProvider, getOthersProviders, getProviderMetadata, isOthersProvider, } from './models/index.js';
|
|
49
49
|
export { assembleTools, deduplicateTools } from './tools.js';
|
|
50
50
|
export { MetaToolsRegistry, createMetaTools, META_TOOLS_SYSTEM_PROMPT_PREFIX, } from './meta-tools/index.js';
|
|
51
51
|
export type { MetaToolStats, MetaTools, FallbackOptions } from './meta-tools/index.js';
|
package/dist/index.js
CHANGED
|
@@ -83,7 +83,7 @@ export { DEFAULT_MODELS, getContextWindow, DEFAULT_CONTEXT_WINDOW } from './mode
|
|
|
83
83
|
// =============================================================================
|
|
84
84
|
// Model Registry, Tiers, Providers (full model system)
|
|
85
85
|
// =============================================================================
|
|
86
|
-
export { MODEL_TIERS, TIER_INFO, isValidTier, MODEL_REGISTRY, getModelsForProvider, getModelsSortedForDisplay, getModelInfo, isKnownModel, isModelSupported, getThinkingFormat, getStatusIndicator, getStatusLabel, getDefaultModelForTier, areThinkingFormatsCompatible, shouldClearHistoryOnModelChange, getModelContextWindow, getModelDisplayName, getModelDescription,
|
|
86
|
+
export { MODEL_TIERS, TIER_INFO, isValidTier, MODEL_REGISTRY, getModelsForProvider, getModelsSortedForDisplay, getModelInfo, isKnownModel, isModelSupported, modelSupportsImages, getThinkingFormat, getStatusIndicator, getStatusLabel, getDefaultModelForTier, areThinkingFormatsCompatible, shouldClearHistoryOnModelChange, getModelContextWindow, getModelDisplayName, getModelDescription,
|
|
87
87
|
// Model tiers (pure, settings-free)
|
|
88
88
|
getModelForTier, getTierMappings, getTierDisplayName, getShortModelName, getDefaultTierMappings, PROVIDER_METADATA, TOGETHER_MODELS, GROQ_MODELS, FIREWORKS_MODELS, PERPLEXITY_MODELS, OPENROUTER_MODELS, getModelsForOthersProvider, getOthersProviders, getProviderMetadata, isOthersProvider, } from './models/index.js';
|
|
89
89
|
// =============================================================================
|
package/dist/models/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Models Module — Barrel Export
|
|
3
3
|
*/
|
|
4
4
|
export { type ModelTier, type TierInfo, type ProviderModelMap, MODEL_TIERS, TIER_INFO, isValidTier, } from './types.js';
|
|
5
|
-
export { type ThinkingFormat, type ModelStatus, type ModelInfo, MODEL_REGISTRY, getModelsForProvider, getModelsSortedForDisplay, getModelInfo, isKnownModel, isModelSupported, getThinkingFormat, getStatusIndicator, getStatusLabel, getDefaultModelForTier, areThinkingFormatsCompatible, shouldClearHistoryOnModelChange, getModelContextWindow, getModelDisplayName, getModelDescription, } from './model-registry.js';
|
|
5
|
+
export { type ThinkingFormat, type ModelStatus, type ModelInfo, MODEL_REGISTRY, getModelsForProvider, getModelsSortedForDisplay, getModelInfo, isKnownModel, isModelSupported, modelSupportsImages, getThinkingFormat, getStatusIndicator, getStatusLabel, getDefaultModelForTier, areThinkingFormatsCompatible, shouldClearHistoryOnModelChange, getModelContextWindow, getModelDisplayName, getModelDescription, } from './model-registry.js';
|
|
6
6
|
export { getModelForTier, getTierMappings, getTierDisplayName, getShortModelName, getDefaultTierMappings, } from './model-tiers.js';
|
|
7
7
|
export { type ProviderMetadata, type OthersProviderModel, PROVIDER_METADATA, TOGETHER_MODELS, GROQ_MODELS, FIREWORKS_MODELS, PERPLEXITY_MODELS, OPENROUTER_MODELS, getModelsForOthersProvider, getOthersProviders, getProviderMetadata, isOthersProvider, } from './providers.js';
|
package/dist/models/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Types & constants
|
|
5
5
|
export { MODEL_TIERS, TIER_INFO, isValidTier, } from './types.js';
|
|
6
6
|
// Model registry
|
|
7
|
-
export { MODEL_REGISTRY, getModelsForProvider, getModelsSortedForDisplay, getModelInfo, isKnownModel, isModelSupported, getThinkingFormat, getStatusIndicator, getStatusLabel, getDefaultModelForTier, areThinkingFormatsCompatible, shouldClearHistoryOnModelChange, getModelContextWindow, getModelDisplayName, getModelDescription, } from './model-registry.js';
|
|
7
|
+
export { MODEL_REGISTRY, getModelsForProvider, getModelsSortedForDisplay, getModelInfo, isKnownModel, isModelSupported, modelSupportsImages, getThinkingFormat, getStatusIndicator, getStatusLabel, getDefaultModelForTier, areThinkingFormatsCompatible, shouldClearHistoryOnModelChange, getModelContextWindow, getModelDisplayName, getModelDescription, } from './model-registry.js';
|
|
8
8
|
// Model tiers (pure, settings-free)
|
|
9
9
|
export { getModelForTier, getTierMappings, getTierDisplayName, getShortModelName, getDefaultTierMappings, } from './model-tiers.js';
|
|
10
10
|
// Provider metadata
|
|
@@ -31,6 +31,9 @@ export interface ModelInfo {
|
|
|
31
31
|
description: string;
|
|
32
32
|
/** Provider this model belongs to */
|
|
33
33
|
provider: ProviderType;
|
|
34
|
+
/** Whether the model accepts image inputs (vision / multimodal). When unset,
|
|
35
|
+
* callers fall back to a provider/id heuristic — see modelSupportsImages(). */
|
|
36
|
+
supportsImages?: boolean;
|
|
34
37
|
/** Default tier mapping (fast/balanced/powerful) - undefined if not a default */
|
|
35
38
|
defaultTier?: ModelTier;
|
|
36
39
|
/** Thinking block format this model uses */
|
|
@@ -72,6 +75,17 @@ export declare function isKnownModel(modelId: string): boolean;
|
|
|
72
75
|
* Returns false for unknown models (conservative default).
|
|
73
76
|
*/
|
|
74
77
|
export declare function isModelSupported(modelId: string): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Whether a model accepts image inputs (vision / multimodal).
|
|
80
|
+
*
|
|
81
|
+
* Uses the registry's `supportsImages` when set; otherwise infers from the
|
|
82
|
+
* provider/id — all modern Claude and Gemini are multimodal, as are OpenAI
|
|
83
|
+
* GPT-4o / GPT-4-turbo / GPT-4.1 / GPT-5; local/text llama·mistral·qwen·deepseek
|
|
84
|
+
* (and search models) are not. Defaults to FALSE for anything unrecognized —
|
|
85
|
+
* safe, because the caller then surfaces a warning rather than silently
|
|
86
|
+
* dropping an attached image (canvas-robustness: image on a text-only model).
|
|
87
|
+
*/
|
|
88
|
+
export declare function modelSupportsImages(modelId: string): boolean;
|
|
75
89
|
/**
|
|
76
90
|
* Get the thinking format for a model.
|
|
77
91
|
* Returns 'none' for unknown models (safe default).
|
|
@@ -24,6 +24,7 @@ export const MODEL_REGISTRY = [
|
|
|
24
24
|
displayName: 'Haiku 4.5',
|
|
25
25
|
description: 'Fast, low cost',
|
|
26
26
|
provider: 'claude',
|
|
27
|
+
supportsImages: true,
|
|
27
28
|
defaultTier: 'fast',
|
|
28
29
|
thinkingFormat: 'claude',
|
|
29
30
|
status: 'supported',
|
|
@@ -34,6 +35,7 @@ export const MODEL_REGISTRY = [
|
|
|
34
35
|
displayName: 'Sonnet 4.6',
|
|
35
36
|
description: 'Balanced (recommended)',
|
|
36
37
|
provider: 'claude',
|
|
38
|
+
supportsImages: true,
|
|
37
39
|
defaultTier: 'balanced',
|
|
38
40
|
thinkingFormat: 'claude',
|
|
39
41
|
status: 'supported',
|
|
@@ -45,6 +47,7 @@ export const MODEL_REGISTRY = [
|
|
|
45
47
|
displayName: 'Opus 4.6',
|
|
46
48
|
description: 'Most capable',
|
|
47
49
|
provider: 'claude',
|
|
50
|
+
supportsImages: true,
|
|
48
51
|
defaultTier: 'powerful',
|
|
49
52
|
thinkingFormat: 'claude',
|
|
50
53
|
status: 'supported',
|
|
@@ -57,6 +60,7 @@ export const MODEL_REGISTRY = [
|
|
|
57
60
|
displayName: 'Sonnet 4.5',
|
|
58
61
|
description: 'Previous generation',
|
|
59
62
|
provider: 'claude',
|
|
63
|
+
supportsImages: true,
|
|
60
64
|
thinkingFormat: 'claude',
|
|
61
65
|
status: 'supported',
|
|
62
66
|
contextWindow: 200000,
|
|
@@ -67,6 +71,7 @@ export const MODEL_REGISTRY = [
|
|
|
67
71
|
displayName: 'Opus 4.5',
|
|
68
72
|
description: 'Previous generation',
|
|
69
73
|
provider: 'claude',
|
|
74
|
+
supportsImages: true,
|
|
70
75
|
thinkingFormat: 'claude',
|
|
71
76
|
status: 'supported',
|
|
72
77
|
contextWindow: 200000,
|
|
@@ -77,6 +82,7 @@ export const MODEL_REGISTRY = [
|
|
|
77
82
|
displayName: 'Sonnet 4',
|
|
78
83
|
description: 'Previous generation',
|
|
79
84
|
provider: 'claude',
|
|
85
|
+
supportsImages: true,
|
|
80
86
|
thinkingFormat: 'claude',
|
|
81
87
|
status: 'supported',
|
|
82
88
|
contextWindow: 200000,
|
|
@@ -92,6 +98,7 @@ export const MODEL_REGISTRY = [
|
|
|
92
98
|
displayName: 'Gemini 2.0 Flash',
|
|
93
99
|
description: 'Fast, no thinking',
|
|
94
100
|
provider: 'gemini',
|
|
101
|
+
supportsImages: true,
|
|
95
102
|
thinkingFormat: 'none',
|
|
96
103
|
status: 'supported',
|
|
97
104
|
contextWindow: 1000000,
|
|
@@ -102,6 +109,7 @@ export const MODEL_REGISTRY = [
|
|
|
102
109
|
displayName: 'Gemini 2.5 Flash Lite',
|
|
103
110
|
description: 'Fast, low cost',
|
|
104
111
|
provider: 'gemini',
|
|
112
|
+
supportsImages: true,
|
|
105
113
|
defaultTier: 'fast',
|
|
106
114
|
thinkingFormat: 'gemini-v2',
|
|
107
115
|
status: 'supported',
|
|
@@ -112,6 +120,7 @@ export const MODEL_REGISTRY = [
|
|
|
112
120
|
displayName: 'Gemini 2.5 Flash',
|
|
113
121
|
description: 'Balanced (recommended)',
|
|
114
122
|
provider: 'gemini',
|
|
123
|
+
supportsImages: true,
|
|
115
124
|
defaultTier: 'balanced',
|
|
116
125
|
thinkingFormat: 'gemini-v2',
|
|
117
126
|
status: 'supported',
|
|
@@ -122,6 +131,7 @@ export const MODEL_REGISTRY = [
|
|
|
122
131
|
displayName: 'Gemini 2.5 Pro',
|
|
123
132
|
description: 'Most capable',
|
|
124
133
|
provider: 'gemini',
|
|
134
|
+
supportsImages: true,
|
|
125
135
|
defaultTier: 'powerful',
|
|
126
136
|
thinkingFormat: 'gemini-v2',
|
|
127
137
|
status: 'supported',
|
|
@@ -133,6 +143,7 @@ export const MODEL_REGISTRY = [
|
|
|
133
143
|
displayName: 'Gemini 3 Flash (Preview)',
|
|
134
144
|
description: 'Preview - may have stability issues',
|
|
135
145
|
provider: 'gemini',
|
|
146
|
+
supportsImages: true,
|
|
136
147
|
thinkingFormat: 'gemini-v3',
|
|
137
148
|
status: 'supported',
|
|
138
149
|
contextWindow: 1000000,
|
|
@@ -143,6 +154,7 @@ export const MODEL_REGISTRY = [
|
|
|
143
154
|
displayName: 'Gemini 3 Pro (Preview)',
|
|
144
155
|
description: 'Preview - may have stability issues',
|
|
145
156
|
provider: 'gemini',
|
|
157
|
+
supportsImages: true,
|
|
146
158
|
thinkingFormat: 'gemini-v3',
|
|
147
159
|
status: 'supported',
|
|
148
160
|
contextWindow: 1000000,
|
|
@@ -156,6 +168,7 @@ export const MODEL_REGISTRY = [
|
|
|
156
168
|
displayName: 'GPT-4o',
|
|
157
169
|
description: 'Balanced (recommended)',
|
|
158
170
|
provider: 'openai',
|
|
171
|
+
supportsImages: true,
|
|
159
172
|
defaultTier: 'balanced',
|
|
160
173
|
thinkingFormat: 'none',
|
|
161
174
|
status: 'supported',
|
|
@@ -166,6 +179,7 @@ export const MODEL_REGISTRY = [
|
|
|
166
179
|
displayName: 'GPT-4o Mini',
|
|
167
180
|
description: 'Fast, low cost',
|
|
168
181
|
provider: 'openai',
|
|
182
|
+
supportsImages: true,
|
|
169
183
|
defaultTier: 'fast',
|
|
170
184
|
thinkingFormat: 'none',
|
|
171
185
|
status: 'supported',
|
|
@@ -176,6 +190,7 @@ export const MODEL_REGISTRY = [
|
|
|
176
190
|
displayName: 'GPT-4 Turbo',
|
|
177
191
|
description: 'Most capable',
|
|
178
192
|
provider: 'openai',
|
|
193
|
+
supportsImages: true,
|
|
179
194
|
defaultTier: 'powerful',
|
|
180
195
|
thinkingFormat: 'none',
|
|
181
196
|
status: 'supported',
|
|
@@ -187,6 +202,7 @@ export const MODEL_REGISTRY = [
|
|
|
187
202
|
displayName: 'GPT-5 Nano',
|
|
188
203
|
description: 'Fast, experimental',
|
|
189
204
|
provider: 'openai',
|
|
205
|
+
supportsImages: true,
|
|
190
206
|
thinkingFormat: 'none',
|
|
191
207
|
status: 'experimental',
|
|
192
208
|
contextWindow: 128000,
|
|
@@ -197,6 +213,7 @@ export const MODEL_REGISTRY = [
|
|
|
197
213
|
displayName: 'GPT-5 Mini',
|
|
198
214
|
description: 'Balanced, experimental',
|
|
199
215
|
provider: 'openai',
|
|
216
|
+
supportsImages: true,
|
|
200
217
|
thinkingFormat: 'none',
|
|
201
218
|
status: 'experimental',
|
|
202
219
|
contextWindow: 128000,
|
|
@@ -207,6 +224,7 @@ export const MODEL_REGISTRY = [
|
|
|
207
224
|
displayName: 'GPT-5.2',
|
|
208
225
|
description: 'Most capable, experimental',
|
|
209
226
|
provider: 'openai',
|
|
227
|
+
supportsImages: true,
|
|
210
228
|
thinkingFormat: 'none',
|
|
211
229
|
status: 'experimental',
|
|
212
230
|
contextWindow: 128000,
|
|
@@ -499,6 +517,27 @@ export function isModelSupported(modelId) {
|
|
|
499
517
|
const model = getModelInfo(modelId);
|
|
500
518
|
return model?.status === 'supported';
|
|
501
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Whether a model accepts image inputs (vision / multimodal).
|
|
522
|
+
*
|
|
523
|
+
* Uses the registry's `supportsImages` when set; otherwise infers from the
|
|
524
|
+
* provider/id — all modern Claude and Gemini are multimodal, as are OpenAI
|
|
525
|
+
* GPT-4o / GPT-4-turbo / GPT-4.1 / GPT-5; local/text llama·mistral·qwen·deepseek
|
|
526
|
+
* (and search models) are not. Defaults to FALSE for anything unrecognized —
|
|
527
|
+
* safe, because the caller then surfaces a warning rather than silently
|
|
528
|
+
* dropping an attached image (canvas-robustness: image on a text-only model).
|
|
529
|
+
*/
|
|
530
|
+
export function modelSupportsImages(modelId) {
|
|
531
|
+
const info = getModelInfo(modelId);
|
|
532
|
+
if (info?.supportsImages !== undefined)
|
|
533
|
+
return info.supportsImages;
|
|
534
|
+
const id = modelId.toLowerCase();
|
|
535
|
+
if (id.includes('claude') || id.includes('gemini'))
|
|
536
|
+
return true;
|
|
537
|
+
if (/gpt-4o|gpt-4-turbo|gpt-4\.1|gpt-5/.test(id))
|
|
538
|
+
return true;
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
502
541
|
/**
|
|
503
542
|
* Get the thinking format for a model.
|
|
504
543
|
* Returns 'none' for unknown models (safe default).
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* (ICanvasRepository); the current project is resolved like the document tools.
|
|
12
12
|
*/
|
|
13
13
|
import type { PlatformToolsConfig } from '../context.js';
|
|
14
|
-
import type { ControlManifest } from '../../canvas/types.js';
|
|
14
|
+
import type { CanvasType, ControlManifest } from '../../canvas/types.js';
|
|
15
15
|
/**
|
|
16
16
|
* Guard the `html` arg against shapes that CANNOT render in the sandboxed
|
|
17
17
|
* canvas iframe (canvas-robustness-spec §3.3). Returns a corrective error
|
|
@@ -24,6 +24,12 @@ import type { ControlManifest } from '../../canvas/types.js';
|
|
|
24
24
|
* CSP actually blocks.
|
|
25
25
|
*/
|
|
26
26
|
export declare function validateCanvasHtml(html: string): string | null;
|
|
27
|
+
export interface CanvasIssue {
|
|
28
|
+
level: 'error' | 'warn';
|
|
29
|
+
message: string;
|
|
30
|
+
}
|
|
31
|
+
/** Run deterministic static checks on canvas content. No rendering. */
|
|
32
|
+
export declare function runCanvasChecks(html: string, type: CanvasType): CanvasIssue[];
|
|
27
33
|
export declare function createCanvasTools(config: PlatformToolsConfig): (import("@compilr-dev/agents").Tool<{
|
|
28
34
|
type: string;
|
|
29
35
|
title: string;
|
|
@@ -95,6 +95,113 @@ export function validateCanvasHtml(html) {
|
|
|
95
95
|
}
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
98
|
+
/** Read a numeric `prop: Npx` from an inline style string. */
|
|
99
|
+
function pxProp(style, prop) {
|
|
100
|
+
const m = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*(-?[\\d.]+)px`, 'i').exec(style);
|
|
101
|
+
return m ? parseFloat(m[1]) : null;
|
|
102
|
+
}
|
|
103
|
+
/** Parse a board's declared width/height from its `data-board` element's style. */
|
|
104
|
+
function parseBoardBounds(tag) {
|
|
105
|
+
const styleM = /\bstyle\s*=\s*(['"])([\s\S]*?)\1/i.exec(tag);
|
|
106
|
+
if (!styleM)
|
|
107
|
+
return null;
|
|
108
|
+
const w = pxProp(styleM[2], 'width');
|
|
109
|
+
const h = pxProp(styleM[2], 'height');
|
|
110
|
+
return w !== null && h !== null ? { w, h } : null;
|
|
111
|
+
}
|
|
112
|
+
/** Best-effort: inline-positioned nodes (left+top px) that fall outside bounds. */
|
|
113
|
+
function offCanvasNodes(src, bounds) {
|
|
114
|
+
const out = [];
|
|
115
|
+
const re = /style\s*=\s*(['"])([^'"]*?\bleft\s*:\s*-?[\d.]+px[^'"]*?)\1/gi;
|
|
116
|
+
let m;
|
|
117
|
+
let seen = 0;
|
|
118
|
+
while ((m = re.exec(src)) !== null && seen < 200 && out.length < 5) {
|
|
119
|
+
seen++;
|
|
120
|
+
const style = m[2];
|
|
121
|
+
const left = pxProp(style, 'left');
|
|
122
|
+
const top = pxProp(style, 'top');
|
|
123
|
+
if (left === null || top === null)
|
|
124
|
+
continue;
|
|
125
|
+
const w = pxProp(style, 'width') ?? 0;
|
|
126
|
+
const h = pxProp(style, 'height') ?? 0;
|
|
127
|
+
if (left < 0 || top < 0 || left + w > bounds.w + 1 || top + h > bounds.h + 1) {
|
|
128
|
+
out.push(`A node at (${String(Math.round(left))}, ${String(Math.round(top))})` +
|
|
129
|
+
(w || h ? ` sized ${String(Math.round(w))}×${String(Math.round(h))}` : '') +
|
|
130
|
+
` extends outside the ${String(bounds.w)}×${String(bounds.h)} board — move it inside or enlarge data-board.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
/** Run deterministic static checks on canvas content. No rendering. */
|
|
136
|
+
export function runCanvasChecks(html, type) {
|
|
137
|
+
const issues = [];
|
|
138
|
+
const src = html.trim();
|
|
139
|
+
if (!src) {
|
|
140
|
+
issues.push({ level: 'error', message: 'Canvas content is empty.' });
|
|
141
|
+
return issues;
|
|
142
|
+
}
|
|
143
|
+
const hasTag = /<[a-z][\s\S]*>/i.test(src);
|
|
144
|
+
if (!hasTag) {
|
|
145
|
+
issues.push({
|
|
146
|
+
level: 'error',
|
|
147
|
+
message: 'No HTML/SVG tags — the canvas renders raw HTML/SVG, not plain text or Markdown.',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Won't-render shapes (Mermaid / Markdown / external CDN) — reuse the write guard.
|
|
151
|
+
const shape = validateCanvasHtml(src);
|
|
152
|
+
if (shape)
|
|
153
|
+
issues.push({ level: 'error', message: shape });
|
|
154
|
+
// Sparse / likely-incomplete.
|
|
155
|
+
const elementCount = (src.match(/<[a-z][a-z0-9]*\b/gi) ?? []).length;
|
|
156
|
+
if (hasTag && (src.length < 200 || elementCount < 3)) {
|
|
157
|
+
issues.push({
|
|
158
|
+
level: 'warn',
|
|
159
|
+
message: `Looks sparse (${String(elementCount)} elements, ${String(src.length)} chars) — is the canvas fully composed?`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// Theme-awareness: hardcoded colors with no theme tokens won't adapt to the user's theme.
|
|
163
|
+
const hardColors = (src.match(/#[0-9a-f]{3,8}\b|\b(?:rgb|rgba|hsl|hsla)\s*\(/gi) ?? []).length;
|
|
164
|
+
const themeTokens = (src.match(/var\(\s*--canvas-/gi) ?? []).length;
|
|
165
|
+
if (hardColors >= 6 && themeTokens === 0) {
|
|
166
|
+
issues.push({
|
|
167
|
+
level: 'warn',
|
|
168
|
+
message: `${String(hardColors)} hardcoded colors and no theme tokens — the canvas won't adapt to the user's theme. ` +
|
|
169
|
+
'Use var(--canvas-accent | secondary | fg | bg | muted | border | card) for themeable surfaces (keep a fixed color only where one is intended).',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// Type-specific structure.
|
|
173
|
+
if (type === 'board') {
|
|
174
|
+
const board = /<[^>]*\bdata-board\b[^>]*>/i.exec(src);
|
|
175
|
+
if (!board) {
|
|
176
|
+
issues.push({
|
|
177
|
+
level: 'error',
|
|
178
|
+
message: 'A board must wrap its nodes in <div data-board style="position:relative;width:Wpx;height:Hpx"> — those bounds drive Fit and Export.',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const bounds = parseBoardBounds(board[0]);
|
|
183
|
+
if (!bounds) {
|
|
184
|
+
issues.push({
|
|
185
|
+
level: 'warn',
|
|
186
|
+
message: 'data-board has no parseable width/height in its style — Fit/Export fall back to a default. Set width:Wpx;height:Hpx.',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
for (const msg of offCanvasNodes(src, bounds))
|
|
191
|
+
issues.push({ level: 'warn', message: msg });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else if (type === 'carousel') {
|
|
196
|
+
if (!/\bdata-sheet\b/i.test(src)) {
|
|
197
|
+
issues.push({
|
|
198
|
+
level: 'warn',
|
|
199
|
+
message: 'No [data-sheet] elements — a carousel paginates on data-sheet blocks; without them it renders as one sheet.',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return issues;
|
|
204
|
+
}
|
|
98
205
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
99
206
|
export function createCanvasTools(config) {
|
|
100
207
|
const ctx = config.context;
|
|
@@ -396,5 +503,85 @@ export function createCanvasTools(config) {
|
|
|
396
503
|
}
|
|
397
504
|
},
|
|
398
505
|
});
|
|
399
|
-
|
|
506
|
+
// ---------------------------------------------------------------------------
|
|
507
|
+
// canvas_validate — deterministic self-review
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
509
|
+
const canvasValidateTool = defineTool({
|
|
510
|
+
name: 'canvas_validate',
|
|
511
|
+
description: 'Self-review a canvas for problems — a fast, deterministic STATIC check (no rendering). ' +
|
|
512
|
+
'Run it right after canvas_write to catch structural/technical faults: content that cannot render ' +
|
|
513
|
+
'(Mermaid/Markdown/external CDN), a board missing its data-board bounds or with nodes off-canvas, ' +
|
|
514
|
+
'sparse/empty output, and hardcoded colors that ignore the theme tokens. Pass canvas_id to check a ' +
|
|
515
|
+
'stored canvas, or html+type to check before writing. It does NOT judge visual/aesthetic quality — ' +
|
|
516
|
+
'errors mean "likely renders broken", warnings are quality nudges.',
|
|
517
|
+
inputSchema: {
|
|
518
|
+
type: 'object',
|
|
519
|
+
properties: {
|
|
520
|
+
canvas_id: {
|
|
521
|
+
type: 'number',
|
|
522
|
+
description: 'Stored canvas to validate. Omit to validate html+type directly.',
|
|
523
|
+
},
|
|
524
|
+
html: {
|
|
525
|
+
type: 'string',
|
|
526
|
+
description: 'Raw HTML/SVG to validate (when not using canvas_id).',
|
|
527
|
+
},
|
|
528
|
+
type: {
|
|
529
|
+
type: 'string',
|
|
530
|
+
enum: CANVAS_TYPES,
|
|
531
|
+
description: 'Canvas type (required when passing html; defaults to infographic).',
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
required: [],
|
|
535
|
+
},
|
|
536
|
+
execute: async (input) => {
|
|
537
|
+
try {
|
|
538
|
+
let html;
|
|
539
|
+
let type;
|
|
540
|
+
if (input.canvas_id !== undefined) {
|
|
541
|
+
const c = await canvases.getById(input.canvas_id);
|
|
542
|
+
if (!c)
|
|
543
|
+
return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
|
|
544
|
+
html = c.content;
|
|
545
|
+
type = c.type;
|
|
546
|
+
}
|
|
547
|
+
else if (input.html !== undefined) {
|
|
548
|
+
html = input.html;
|
|
549
|
+
type = input.type ?? 'infographic';
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
return createErrorResult('Provide canvas_id (a stored canvas) or html (+ type) to validate.');
|
|
553
|
+
}
|
|
554
|
+
const issues = runCanvasChecks(html, type);
|
|
555
|
+
if (issues.length === 0) {
|
|
556
|
+
return createSuccessResult('✓ Passed all static checks — no structural/technical issues. (Visual/aesthetic review is a separate pass.)');
|
|
557
|
+
}
|
|
558
|
+
const errors = issues.filter((i) => i.level === 'error');
|
|
559
|
+
const warns = issues.filter((i) => i.level === 'warn');
|
|
560
|
+
const lines = [];
|
|
561
|
+
if (errors.length > 0) {
|
|
562
|
+
lines.push(`${String(errors.length)} error(s) — likely to render broken:`);
|
|
563
|
+
for (const e of errors)
|
|
564
|
+
lines.push(` ✗ ${e.message}`);
|
|
565
|
+
}
|
|
566
|
+
if (warns.length > 0) {
|
|
567
|
+
lines.push(`${String(warns.length)} warning(s):`);
|
|
568
|
+
for (const w of warns)
|
|
569
|
+
lines.push(` ⚠ ${w.message}`);
|
|
570
|
+
}
|
|
571
|
+
lines.push('', 'Fix the errors (they break rendering); address warnings where they apply, then re-validate.');
|
|
572
|
+
return createSuccessResult(lines.join('\n'));
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
return createErrorResult(`Failed to validate canvas: ${error instanceof Error ? error.message : String(error)}`);
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
return [
|
|
580
|
+
canvasWriteTool,
|
|
581
|
+
canvasEditTool,
|
|
582
|
+
canvasListTool,
|
|
583
|
+
canvasGetTool,
|
|
584
|
+
canvasDeleteTool,
|
|
585
|
+
canvasValidateTool,
|
|
586
|
+
];
|
|
400
587
|
}
|
|
@@ -67,7 +67,9 @@ export const canvasSkill = defineSkill({
|
|
|
67
67
|
|
|
68
68
|
4. **To EDIT an existing canvas, do NOT re-send the whole document.** First call \`canvas_get\` with \`outline=true\` (or a \`startLine\`/\`maxLines\` slice) to find the exact snippet, then \`canvas_edit\` with \`operation=str_replace\` (old_str must be unique — include surrounding context — or set replace_all=true), or append/prepend. Reserve \`canvas_write\` with \`canvas_id\` for a full intentional rewrite.
|
|
69
69
|
|
|
70
|
-
5. **
|
|
70
|
+
5. **Validate before you hand it off.** Once the canvas is composed, call \`canvas_validate\` (canvas_id) — a fast static check that catches content that won't render (Mermaid/Markdown/CDN), a board missing its \`data-board\` bounds or with nodes off-canvas, sparse output, and hardcoded colors that ignore the theme tokens. Fix any **errors** (they render broken) and address applicable **warnings**, then move on. (It doesn't judge looks — that's your eye.)
|
|
71
|
+
|
|
72
|
+
6. **After writing, tell the user what you made in one line** and point them at the Tweaks they can adjust. The canvas opens in its own tab.
|
|
71
73
|
|
|
72
74
|
## Rules
|
|
73
75
|
- CALL THE TOOL — don't describe the HTML you "would" write and stop. A canvas only exists once the tool succeeds.
|
package/dist/team/tool-config.js
CHANGED
|
@@ -288,7 +288,14 @@ export const TOOL_GROUPS = {
|
|
|
288
288
|
label: 'Visual Canvas',
|
|
289
289
|
// Canvas tools are defined in @compilr-dev/sdk (createPlatformTools), not in
|
|
290
290
|
// the agents-lib TOOL_NAMES registry, so they are referenced by literal name.
|
|
291
|
-
tools: [
|
|
291
|
+
tools: [
|
|
292
|
+
'canvas_write',
|
|
293
|
+
'canvas_edit',
|
|
294
|
+
'canvas_list',
|
|
295
|
+
'canvas_get',
|
|
296
|
+
'canvas_delete',
|
|
297
|
+
'canvas_validate',
|
|
298
|
+
],
|
|
292
299
|
readOnly: false,
|
|
293
300
|
tier: 'meta',
|
|
294
301
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compilr-dev/sdk",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"description": "Universal agent runtime for building AI-powered applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"./canvas": {
|
|
26
26
|
"types": "./dist/canvas/index.d.ts",
|
|
27
27
|
"import": "./dist/canvas/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./models": {
|
|
30
|
+
"types": "./dist/models/index.d.ts",
|
|
31
|
+
"import": "./dist/models/index.js"
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"files": [
|