@pixerate/editor 0.102.0 → 0.104.0
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/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/agent/index.cjs +7 -7
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +7 -7
- package/dist/canvas/index.cjs +3 -3
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.js +4 -4
- package/dist/chunk-6BU5WU4R.cjs +1 -0
- package/dist/chunk-6BU5WU4R.cjs.map +1 -0
- package/dist/{chunk-IB53LVGV.js → chunk-DS7KDKPX.js} +1 -1
- package/dist/chunk-NQLQ2JOI.js +7176 -0
- package/dist/chunk-NQLQ2JOI.js.map +1 -0
- package/dist/{chunk-ZZOTHJ4K.cjs → chunk-PV42JITT.cjs} +1 -1
- package/dist/{chunk-ZZOTHJ4K.cjs.map → chunk-PV42JITT.cjs.map} +1 -1
- package/dist/chunk-VSMM7B3P.cjs +7176 -0
- package/dist/chunk-VSMM7B3P.cjs.map +1 -0
- package/dist/chunk-XYBJKUAU.js +1 -0
- package/dist/chunk-XYBJKUAU.js.map +1 -0
- package/dist/compositor-B1ZrKR89.d.ts +835 -0
- package/dist/compositor-CTMU-B1t.d.cts +835 -0
- package/dist/export-BLpMyCYg.d.ts +3139 -0
- package/dist/export-X9rY79p0.d.cts +3139 -0
- package/dist/homography-CZ-tFjay.d.cts +50 -0
- package/dist/homography-Ctfjoc1b.d.ts +50 -0
- package/dist/image-editor/index.cjs +2846 -9407
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +24 -3151
- package/dist/image-editor/index.d.ts +24 -3151
- package/dist/image-editor/index.js +2508 -9069
- package/dist/image-editor/index.js.map +1 -1
- package/dist/mixer-DG_9Yu2W.d.ts +282 -0
- package/dist/mixer-DGcH6HLu.d.cts +282 -0
- package/dist/paint/index.cjs +4 -3
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +3 -281
- package/dist/paint/index.d.ts +3 -281
- package/dist/paint/index.js +2 -1
- package/dist/paint-editor/index.cjs +4417 -0
- package/dist/paint-editor/index.cjs.map +1 -0
- package/dist/paint-editor/index.d.cts +1539 -0
- package/dist/paint-editor/index.d.ts +1539 -0
- package/dist/paint-editor/index.js +4417 -0
- package/dist/paint-editor/index.js.map +1 -0
- package/dist/raster/index.d.cts +6 -835
- package/dist/raster/index.d.ts +6 -835
- package/dist/{surface-DIA-XatS.d.cts → surface-AfHCH7BR.d.cts} +1 -1
- package/dist/{surface-DIA-XatS.d.ts → surface-AfHCH7BR.d.ts} +1 -1
- package/dist/tools/index.cjs +3 -3
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/vector/index.d.cts +2 -1
- package/dist/vector/index.d.ts +2 -1
- package/dist/{warp-B8rAguzs.d.ts → warp-CWHZzE3x.d.ts} +2 -49
- package/dist/{warp-nYs2DPrx.d.cts → warp-D5jz_tN7.d.cts} +2 -49
- package/package.json +6 -1
- package/skills/README.md +1 -0
- package/skills/pixerate-image-editor/SKILL.md +14 -5
- package/skills/pixerate-paint-editor/SKILL.md +203 -0
- /package/dist/{chunk-IB53LVGV.js.map → chunk-DS7KDKPX.js.map} +0 -0
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -182,6 +182,8 @@ GridCraft. Apache POI is © The Apache Software Foundation, licensed under the A
|
|
|
182
182
|
- `crates/raster/src/lib.rs` (`Surface`: a sparse map of 256 px tiles behind shared pointers with a default pixel, copy-on-write writes that share untouched tiles, uniform tiles stored once, `prune` of tiles equal to the default, `content_bounds`, `translated`, region read / write) → `TiledSurface`, `SurfaceDraft`, `readRegion`, `writeRegion`, `fillRect`, `contentBounds` and `translateSurface` in `packages/core/src/raster/surface.ts`; 8-bit RGBA / 1-byte mask tiles instead of photocraft's pixel formats
|
|
183
183
|
- `crates/paint/src/lib.rs` (`TIP_SHAPE`, `tip_shape`, `soft_exp`, `tip_falloff`, `tip_reach`, `dab_coverage`: Photoshop's measured round tip; `smooth`), `crates/paint/src/dynamics.rs` (`PathWalker`: the first point as a dab, then one every spacing × diameter along the path; `base_diameter` and the Transfer pressure-to-opacity rule) and `crates/paint/src/render.rs` (`rasterize_into` for the round computed tip with a 4 × 4 sub-pixel grid below 3 px; `CoverageMap::accumulate`: flow building up towards each dab's opacity ceiling; `composite`: one composite at the stroke opacity over the pre-stroke pixels, the selection multiplying coverage, the eraser and lock transparency) → `packages/core/src/paint/brush.ts`; only the computed round tip, pressure size and opacity, and simple smoothing are ported (no sampled tips, jitter, texture, dual brush, wet edges or dithering); brush test cases re-expressed → `packages/core/tests/image-layers-paint.test.ts`
|
|
184
184
|
- `crates/paint/src/mixer.rs` (`MixerSettings`, `MixerState`, `PICKUP_RATE`, `apply_mixer_stroke`: per dab, sample the canvas under the footprint by coverage and alpha, move the pickup colour towards it by the pick-up rate × wet, mix the reservoir (weight amount × (1 − mix·wet)) with the pickup (weight mix·wet), deposit at flow × the total weight, drain the reservoir by 8 % × (1 − load) per dab; `load` / `clean`) → `MixerStroke`, `normalizeMixer`, `loadMixer` and `cleanMixer` in `packages/core/src/paint/mixer.ts`; our additions are the pigment mixing model for pickup, paint and deposit, load / clean after each stroke, and tiles in place of photocraft's surfaces (no Sample All Layers or Transfer › Wetness yet); mixer test cases re-expressed → `packages/core/tests/paint-mixer.test.ts`
|
|
185
|
+
- `crates/paint/src/procedural.rs` (`smooth`, the tileable `value_noise` on a wrapped lattice, `fbm` doubling the lattice per octave, the `Paper` and `Canvas` pattern styles: fractal noise with per-pixel grain, and an over / under weave of sine threads) and `crates/paint/src/rng.rs` (counter-based randomness: every value a pure hash of seed, index and stream, so a stroke's jitter replays however its points are chunked) → `TileableNoise`, `hash2`, `rand01` in `packages/core/src/paint-editor/noise.ts` and the `cold-pressed` and `canvas` grain patterns in `packages/core/src/paint-editor/paper.ts`; on 32-bit integer hashes instead of SplitMix64, with unequal lattice periods per axis for fibres and streaks; the other paper patterns, the paper library and the grain response of dry media are our own. The paint editor's `PenDabWalker` extends our port of `crates/paint/src/dynamics.rs`'s `PathWalker` (above) with tilt, barrel rotation and velocity
|
|
186
|
+
- `crates/paint/src/symmetry.rs` (`SymmetryMode` vertical / horizontal / dual / diagonal, `PresetSymmetry::directions` with each mode's initial axis and the rotation added, `reflected_passes`: reflection about the centre along each axis plus the point reflection for dual, `segments`: the axes clipped to the document without assuming the centre is inside, and the coordinate limit) → `symmetryAxisAngles`, `symmetryCopies` and `symmetryAxisSegments` in `packages/core/src/paint-editor/guides.ts`, as affine maps of the stroke's points so one stroke paints every copy; our additions are the kaleidoscope (n rotations and the mirrored segments) and perspective guides; symmetry test cases re-expressed → `packages/core/tests/paint-editor-guides.test.ts`
|
|
185
187
|
- `crates/engine/src/layer_mask_props_cmds.rs` and `crates/compose/src/masks.rs` (non-destructive mask density and feather) → `packages/core/src/image-editor/selection/layer-mask.ts`
|
|
186
188
|
- Test cases from `crates/algo/src/selection.rs`, `crates/algo/src/selection_blur.rs`, `crates/algo/src/matting.rs` and `crates/algo/src/tests_selection_mix.rs`, re-expressed → `packages/core/tests/image-editor-selection.test.ts`, `packages/core/tests/image-editor-selection-controller.test.ts`
|
|
187
189
|
- `crates/compose/src/gradient_fill.rs` (`midpoint_remap`; `Ramp`; `handles` / `from_handles`: the start and end points of the Linear, Radial, Angle, Reflected and Diamond styles) and `crates/compose/src/effects.rs` (`gradient_t`: Reflected as `|u|`, Diamond as `|along| + |across|`, Angle as a clockwise sweep from the gradient's direction) → `gradientMidpointRemap`, `vectorGradientParam` and the reflected / diamond / conic styles in `packages/core/src/vector/gradient.ts`; the styles share vectorcraft's start / end frame instead of an angle, scale and centre offset
|
|
@@ -198,6 +200,7 @@ GridCraft. Apache POI is © The Apache Software Foundation, licensed under the A
|
|
|
198
200
|
- `crates/engine/src/transform_cmds.rs` (`split_selected` and `split_gray_selected`: lifting the selected pixels and the same part of a mask as grey + coverage, composited back over the rest; linked masks moving with their pixels and an unlinked mask transformed alone; position locks refusing a transform) and `crates/io/src/psd_import.rs` / `psd_export.rs` (mask flag bit 0 as an unlinked mask) → `packages/core/src/image-editor/free-transform.ts` and `packages/core/src/image-editor/psd/`; test cases re-expressed → `packages/core/tests/image-free-transform.test.ts`
|
|
199
201
|
- `crates/io/src/effects_map.rs` (`parse_lfx2` / `write_lfx2`: the per-kind descriptor keys, the single and `…Multi` instance keys, `present` / `enab`, blend ids as four-character codes or long string ids, contours with corner points, `parse_lrfx` for legacy effects), `crates/io/src/blocks.rs` (`color_from_desc` / `color_to_desc`, `gradient_style`, the `Grdn` colour and transparency stops, `pattern_ref`, `pattern_placement`), `crates/io/src/psd_import.rs` / `psd_export.rs` (`lmfx` over `lfx2` / `lfxs`, the block kept and written back while the effects are unchanged, odd-length blocks padded) and `crates/psd/src/patterns.rs` (`Patt` / `Pat2` / `Pat3` blocks: patterns, virtual memory array lists, written channels, padded channel rectangles) → `packages/core/src/image-editor/psd/effects.ts` and `patterns.ts`; gradient midpoints become extra stops and smoothness is drawn linearly (photocraft bakes Photoshop's interpolation), and patterns are written as 8-bit RGB; test cases re-expressed → `packages/core/tests/image-psd-effects.test.ts`
|
|
200
202
|
- `crates/doc/src/lib.rs` (`SmartObject`: contents shared by an identity that ordinary duplicates keep and New Smart Object via Copy renews, the embedded or linked `SmartSource`, the placement transform with a warp applied first in source pixels and a projective override, the ordered `smart_filters` with blend, opacity and visibility, the stack-wide `filters_enabled` switch and the one `filter_mask`, the rendered `cache`) → `SmartContents`, `SmartPlacement`, `SmartFilterStack` and `SmartObjectLayer` in `packages/core/src/image-editor/layers/model.ts` and the operations in `layers/smart.ts`; the placement is our Free Transform geometry (corners plus a Bézier mesh), the smart filters are our `develop` / `blur` layer filters, and rendering, caching and Edit Contents as a nested document are our own; test cases re-expressed → `packages/core/tests/image-smart-objects.test.ts`
|
|
203
|
+
- `crates/io/src/smart_map.rs` (the `soLD` descriptor keys `Idnt`, `placed`, `Trnf`, `nonAffineTransform`, `Sz `, `warp`, `filterFX`; `sold_bytes` with a template descriptor and a fresh one, `plld_bytes`, `warp_desc` for single-patch custom warps, `filter_from_item` / `item_for_filter` with neutral blending for unmodelled filters, `filter_fx`, `filter_mask_item`), `crates/io/src/blocks.rs` (`placed_frame`: a custom warp's control-point box as the quad's frame, `corners_affine` / `corners_perspective`, `parse_plld`, `warp_of`), `crates/io/src/linked.rs` (`liFD` items, `encode_linked_file`) and `crates/psd/src/filter_effects.rs` (`FEid` / `FXid` items, planes and masks) → `packages/core/src/image-editor/psd/smart.ts` and the smart-object parts of `psd/import.ts` and `psd/export.ts`; Box Blur is the one filter we draw (Camera Raw and the rest are kept, not drawn), warps are refitted onto the contents' rectangle, linked (`liFE`) contents come in as the stored pixels, and filter masks are written without the unfiltered cache; test cases re-expressed → `packages/core/tests/image-psd-smart.test.ts`
|
|
201
204
|
|
|
202
205
|
## soundcraft
|
|
203
206
|
|
package/dist/agent/index.cjs
CHANGED
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
var _chunkDK6QBGCScjs = require('../chunk-DK6QBGCS.cjs');
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
var _chunkTPOT575Pcjs = require('../chunk-TPOT575P.cjs');
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
var _chunkWADCJ7A7cjs = require('../chunk-WADCJ7A7.cjs');
|
|
14
|
+
require('../chunk-26BVFKHU.cjs');
|
|
11
15
|
require('../chunk-FURT6ZFZ.cjs');
|
|
12
16
|
|
|
13
17
|
|
|
@@ -24,6 +28,7 @@ require('../chunk-FURT6ZFZ.cjs');
|
|
|
24
28
|
var _chunkQIWFEEWGcjs = require('../chunk-QIWFEEWG.cjs');
|
|
25
29
|
|
|
26
30
|
|
|
31
|
+
var _chunkRIWPYSKUcjs = require('../chunk-RIWPYSKU.cjs');
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
|
|
@@ -47,25 +52,20 @@ var _chunkQIWFEEWGcjs = require('../chunk-QIWFEEWG.cjs');
|
|
|
47
52
|
|
|
48
53
|
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var _chunkEI2MOVQ3cjs = require('../chunk-EI2MOVQ3.cjs');
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
|
|
59
|
+
var _chunkEI2MOVQ3cjs = require('../chunk-EI2MOVQ3.cjs');
|
|
57
60
|
|
|
58
61
|
|
|
59
62
|
|
|
60
63
|
|
|
61
|
-
var _chunkVJUVNERTcjs = require('../chunk-VJUVNERT.cjs');
|
|
62
64
|
|
|
63
65
|
|
|
64
|
-
var _chunkTPOT575Pcjs = require('../chunk-TPOT575P.cjs');
|
|
65
|
-
require('../chunk-26BVFKHU.cjs');
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
var
|
|
68
|
+
var _chunkVJUVNERTcjs = require('../chunk-VJUVNERT.cjs');
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
|
package/dist/agent/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/agent/index.cjs","../../src/agent/planner.ts","../../src/agent/providers.ts","../../src/agent/documentChartTools.ts","../../src/agent/componentTools.ts"],"names":["range","WRITE"],"mappings":"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CA,MAAA;AACc,QAAA;AACH,QAAA;AACP,QAAA;AACA,QAAA;AACoE,QAAA;AACxE,MAAA;AAEC,MAAA;AACwC,QAAA;AACO,UAAA;AACsC,UAAA;AAChD,UAAA;AACnC,QAAA;AACF,MAAA;AAC2B,MAAA;AAC7B,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACa,QAAA;AACL,UAAA;AACgB,UAAA;AACT,UAAA;AACd,QAAA;AACK,QAAA;AACN,MAAA;AACgF,MAAA;AACrD,MAAA;AAC5B,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACsC,MAAA;AACmC,MAAA;AAC7C,MAAA;AAC7B,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACgD,MAAA;AACa,MAAA;AAC9D,IAAA;AACA,IAAA;AAAK,MAAA;AAAkB,MAAA;AAAmG,MAAA;AACvF,MAAA;AACnC,IAAA;AAC6F,IAAA;AAChE,IAAA;AACgB,MAAA;AACkB,MAAA;AAC5C,MAAA;AAClB,IAAA;AACD,IAAA;AAAK,MAAA;AAA0B,MAAA;AAA6F,MAAA;AAClF,MAAA;AAC1C,IAAA;AACD,EAAA;AACoC,EAAA;AACQ,IAAA;AACI,IAAA;AACyC,IAAA;AAClF,IAAA;AACL,MAAA;AACC,QAAA;AACA,QAAA;AACuF,QAAA;AAEtF,QAAA;AAC8B,UAAA;AACS,YAAA;AACR,YAAA;AACsD,YAAA;AACnF,UAAA;AACF,QAAA;AACF,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACc,QAAA;AACgC,QAAA;AAC/C,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AAC8B,QAAA;AACuC,QAAA;AAC9C,QAAA;AACxB,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AAC+D,UAAA;AACD,UAAA;AACtD,UAAA;AACR,QAAA;AACyF,QAAA;AACzD,QAAA;AACjC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACmE,UAAA;AACX,UAAA;AACD,UAAA;AAC/C,UAAA;AACR,QAAA;AACwF,QAAA;AAC1C,QAAA;AAC/C,MAAA;AACD,IAAA;AACD,EAAA;AAC6D,EAAA;AAC9D;AJ65CiG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/agent/index.cjs","sourcesContent":[null,"import { ensureLayout, getLayoutedNodes, placeBeside } from '../canvas/layout.js';\nimport type { CanvasEdge, CanvasNode, Rect, Side } from '../canvas/types.js';\nimport type { AgentCommand, AgentStep, EditorSnapshot } from './types.js';\n\n/**\n * Normalizes a placement side from either the planner vocabulary (`right | below | left | above`)\n * or the MCP tool schema vocabulary (`top | right | bottom | left`). Unknown values fall back to `right`.\n */\nexport function normalizeSide(side: string | undefined | null): Side {\n\tswitch (side) {\n\t\tcase 'top':\n\t\tcase 'above':\n\t\t\treturn 'above';\n\t\tcase 'bottom':\n\t\tcase 'below':\n\t\t\treturn 'below';\n\t\tcase 'left':\n\t\t\treturn 'left';\n\t\tcase 'right':\n\t\tdefault:\n\t\t\treturn 'right';\n\t}\n}\n\n/**\n * Plans a sequence of atomic execution steps from a high-level agent command.\n */\nexport function planAgentCommand<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tcommand: AgentCommand<TNode, TEdge>,\n\tsnapshot: EditorSnapshot<TNode, TEdge>,\n\toptions: {\n\t\tdefaultNodeSize?: { width: number; height: number };\n\t\tidGenerator?: () => string;\n\t} = {}\n): AgentStep<TNode, TEdge>[] {\n\tconst defaultNodeSize = options.defaultNodeSize ?? { width: 200, height: 60 };\n\tconst idGen = options.idGenerator ?? (() => `node-${Math.random().toString(36).substring(2, 9)}`);\n\tconst edgeIdGen = () => `edge-${Math.random().toString(36).substring(2, 9)}`;\n\n\tconst nodes = snapshot.canvas?.nodes ?? [];\n\tconst edges = snapshot.canvas?.edges ?? [];\n\n\tswitch (command.type) {\n\t\tcase 'canvas:add_node': {\n\t\t\tlet pos = command.node.position;\n\t\t\t// Only anchor to (and connect from) a node that actually exists.\n\t\t\tconst anchorId =\n\t\t\t\tcommand.nearNodeId && nodes.some((n) => n.id === command.nearNodeId)\n\t\t\t\t\t? command.nearNodeId\n\t\t\t\t\t: undefined;\n\n\t\t\tif (!pos) {\n\t\t\t\tconst rects: Record<string, Rect> = {};\n\t\t\t\tfor (const n of nodes) {\n\t\t\t\t\trects[n.id] = {\n\t\t\t\t\t\tx: n.position.x,\n\t\t\t\t\t\ty: n.position.y,\n\t\t\t\t\t\twidth: n.measured?.width ?? (n as any).width ?? defaultNodeSize.width,\n\t\t\t\t\t\theight: n.measured?.height ?? (n as any).height ?? defaultNodeSize.height\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (anchorId) {\n\t\t\t\t\t// Use collision-aware directional placement\n\t\t\t\t\tpos = placeBeside(rects, defaultNodeSize, normalizeSide(command.side), anchorId);\n\t\t\t\t} else if (nodes.length > 0) {\n\t\t\t\t\t// Default beside the last node\n\t\t\t\t\tconst lastNode = nodes[nodes.length - 1];\n\t\t\t\t\tpos = placeBeside(rects, defaultNodeSize, 'right', lastNode.id);\n\t\t\t\t} else {\n\t\t\t\t\tpos = { x: 100, y: 100 };\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst fullNode: TNode = {\n\t\t\t\t...command.node,\n\t\t\t\tid: command.node.id || idGen(),\n\t\t\t\tposition: pos\n\t\t\t} as TNode;\n\n\t\t\tconst steps: AgentStep<TNode, TEdge>[] = [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_add_node',\n\t\t\t\t\tnode: fullNode\n\t\t\t\t}\n\t\t\t];\n\n\t\t\t// If attached to nearNode, optionally automatically connect\n\t\t\tif (anchorId) {\n\t\t\t\tsteps.push({\n\t\t\t\t\ttype: 'step:canvas_add_edge',\n\t\t\t\t\tedge: {\n\t\t\t\t\t\tid: edgeIdGen(),\n\t\t\t\t\t\tsource: anchorId,\n\t\t\t\t\t\ttarget: fullNode.id\n\t\t\t\t\t} as TEdge\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn steps;\n\t\t}\n\n\t\tcase 'canvas:remove_nodes': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_remove_nodes',\n\t\t\t\t\tnodeIds: command.nodeIds\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:connect': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_add_edge',\n\t\t\t\t\tedge: {\n\t\t\t\t\t\tid: edgeIdGen(),\n\t\t\t\t\t\tsource: command.source,\n\t\t\t\t\t\ttarget: command.target,\n\t\t\t\t\t\tlabel: command.label\n\t\t\t\t\t} as TEdge\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:update_node': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_update_node',\n\t\t\t\t\tid: command.id,\n\t\t\t\t\tdata: command.data\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:layout': {\n\t\t\tconst dir = command.direction ?? 'LR';\n\t\t\tlet layouted: TNode[];\n\n\t\t\t// Matches the MCP tool schema: `incremental` defaults to true.\n\t\t\tif (command.incremental ?? true) {\n\t\t\t\tconst stored: Record<string, { x: number; y: number }> = {};\n\t\t\t\tfor (const n of nodes) {\n\t\t\t\t\tif (n.position && (n.position.x !== 0 || n.position.y !== 0)) {\n\t\t\t\t\t\tstored[n.id] = { ...n.position };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlayouted = ensureLayout(nodes, edges, {\n\t\t\t\t\tdirection: dir,\n\t\t\t\t\tstoredPositions: stored\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tlayouted = getLayoutedNodes(nodes, edges, undefined, { direction: dir });\n\t\t\t}\n\n\t\t\tconst positions: Record<string, { x: number; y: number }> = {};\n\t\t\tfor (const node of layouted) {\n\t\t\t\tpositions[node.id] = { ...node.position };\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_move_nodes',\n\t\t\t\t\tpositions\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:select': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_select',\n\t\t\t\t\tnodeIds: command.nodeIds\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:highlight': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_highlight',\n\t\t\t\t\tnodeIds: command.nodeIds,\n\t\t\t\t\tcolor: command.color ?? '#3b82f6',\n\t\t\t\t\tdurationMs: command.durationMs ?? 2000\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'prompt:insert_text': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:prompt_insert',\n\t\t\t\t\ttext: command.text,\n\t\t\t\t\tat: command.at\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'prompt:replace_range': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:prompt_replace',\n\t\t\t\t\tfrom: command.from,\n\t\t\t\t\tto: command.to,\n\t\t\t\t\ttext: command.text\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'prompt:set_variable': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:prompt_set_variable',\n\t\t\t\t\tname: command.name,\n\t\t\t\t\tvalue: command.value\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'prompt:select': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:prompt_select',\n\t\t\t\t\tfrom: command.from,\n\t\t\t\t\tto: command.to\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'choreography:say': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:say',\n\t\t\t\t\ttext: command.text,\n\t\t\t\t\tatNodeId: command.atNodeId,\n\t\t\t\t\tposition: command.position\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'choreography:wait': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:wait',\n\t\t\t\t\tms: command.ms\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'tool:call': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:tool_call',\n\t\t\t\t\tname: command.name,\n\t\t\t\t\targuments: command.arguments ?? {}\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tdefault: {\n\t\t\treturn [];\n\t\t}\n\t}\n}\n\nconst CANVAS_TOOL_OPERATIONS: Record<string, string> = {\n\tcanvas_add_node: 'add_node',\n\tcanvas_remove_nodes: 'remove_nodes',\n\tcanvas_connect: 'connect',\n\tcanvas_update_node: 'update_node',\n\tcanvas_layout: 'layout',\n\tcanvas_select: 'select',\n\tcanvas_highlight: 'highlight'\n};\n\nconst PROMPT_TOOL_OPERATIONS: Record<string, string> = {\n\tprompt_insert_text: 'insert_text',\n\tprompt_replace_range: 'replace_range',\n\tprompt_set_variable: 'set_variable',\n\tprompt_select: 'select'\n};\n\n/**\n * Converts an MCP tool call into a planner command. `edit_canvas` /\n * `edit_prompt` and the per-operation `canvas_*` / `prompt_*` tools (see\n * `tools/definitions.ts`) map to canvas/prompt commands using the MCP\n * schema's vocabulary; every other tool, including dynamic `ToolRegistry`\n * tools, becomes a `tool:call` command. Returns `null` when a static tool's\n * arguments are missing what the operation needs.\n */\nexport function toolCallToAgentCommand<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(name: string, args: Record<string, any> = {}): AgentCommand<TNode, TEdge> | null {\n\tconst ids = (value: unknown): string[] | null =>\n\t\tArray.isArray(value) && value.every((v) => typeof v === 'string') ? value : null;\n\tconst num = (value: unknown) => typeof value === 'number' && Number.isFinite(value);\n\n\tconst canvasOperation = name === 'edit_canvas' ? args.operation : CANVAS_TOOL_OPERATIONS[name];\n\tif (canvasOperation !== undefined) {\n\t\tswitch (canvasOperation) {\n\t\t\tcase 'add_node':\n\t\t\t\treturn args.node && typeof args.node === 'object' && !Array.isArray(args.node)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttype: 'canvas:add_node',\n\t\t\t\t\t\t\tnode: { ...args.node, data: args.node.data ?? {} },\n\t\t\t\t\t\t\tnearNodeId: args.nearNodeId,\n\t\t\t\t\t\t\tside: args.side\n\t\t\t\t\t\t}\n\t\t\t\t\t: null;\n\t\t\tcase 'remove_nodes':\n\t\t\t\treturn ids(args.nodeIds) ? { type: 'canvas:remove_nodes', nodeIds: args.nodeIds } : null;\n\t\t\tcase 'connect':\n\t\t\t\treturn typeof args.source === 'string' && typeof args.target === 'string'\n\t\t\t\t\t? { type: 'canvas:connect', source: args.source, target: args.target, label: args.label }\n\t\t\t\t\t: null;\n\t\t\tcase 'update_node': {\n\t\t\t\tconst id = args.node?.id ?? args.id;\n\t\t\t\treturn typeof id === 'string'\n\t\t\t\t\t? { type: 'canvas:update_node', id, data: args.node?.data ?? args.data ?? {} }\n\t\t\t\t\t: null;\n\t\t\t}\n\t\t\tcase 'layout':\n\t\t\t\treturn { type: 'canvas:layout', direction: args.direction, incremental: args.incremental };\n\t\t\tcase 'select':\n\t\t\t\treturn ids(args.nodeIds) ? { type: 'canvas:select', nodeIds: args.nodeIds } : null;\n\t\t\tcase 'highlight':\n\t\t\t\treturn ids(args.nodeIds)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttype: 'canvas:highlight',\n\t\t\t\t\t\t\tnodeIds: args.nodeIds,\n\t\t\t\t\t\t\t...(typeof args.color === 'string' ? { color: args.color } : {}),\n\t\t\t\t\t\t\t...(num(args.durationMs) ? { durationMs: args.durationMs } : {})\n\t\t\t\t\t\t}\n\t\t\t\t\t: null;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\tconst promptOperation = name === 'edit_prompt' ? args.operation : PROMPT_TOOL_OPERATIONS[name];\n\tif (promptOperation !== undefined) {\n\t\tswitch (promptOperation) {\n\t\t\tcase 'insert_text':\n\t\t\t\treturn typeof args.text === 'string'\n\t\t\t\t\t? { type: 'prompt:insert_text', text: args.text, at: num(args.at) ? args.at : undefined }\n\t\t\t\t\t: null;\n\t\t\tcase 'replace_range':\n\t\t\t\treturn typeof args.text === 'string' && num(args.from) && num(args.to)\n\t\t\t\t\t? { type: 'prompt:replace_range', from: args.from, to: args.to, text: args.text }\n\t\t\t\t\t: null;\n\t\t\tcase 'set_variable':\n\t\t\t\treturn typeof args.name === 'string'\n\t\t\t\t\t? { type: 'prompt:set_variable', name: args.name, value: args.value }\n\t\t\t\t\t: null;\n\t\t\tcase 'select':\n\t\t\t\treturn num(args.from) && num(args.to) ? { type: 'prompt:select', from: args.from, to: args.to } : null;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\treturn { type: 'tool:call', name, arguments: args };\n}\n\n/**\n * Checks a command against the snapshot before it is planned and returns a\n * message an agent can act on, or `null` when it is valid: node ids must\n * exist (and a new node's id must not), an edge needs two different existing\n * nodes, and prompt ranges must be ordered. Prompt positions are host-defined,\n * so their upper bound is left to the host's `applySteps`.\n * The planner itself stays lenient (it falls back instead of failing), so\n * tool providers call this first.\n */\nexport function validateAgentCommand<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(command: AgentCommand<TNode, TEdge>, snapshot: EditorSnapshot<TNode, TEdge>): string | null {\n\tconst known = new Set((snapshot.canvas?.nodes ?? []).map((n) => n.id));\n\tconst missing = (nodeIds: readonly string[]) => {\n\t\tconst unknown = nodeIds.filter((id) => !known.has(id));\n\t\treturn unknown.length\n\t\t\t? `Unknown node id${unknown.length > 1 ? 's' : ''}: ${unknown.map((id) => JSON.stringify(id)).join(', ')}. Call get_editor_state for the current ids.`\n\t\t\t: null;\n\t};\n\tconst range = (from: number, to: number) => {\n\t\tif (from < 0 || to < 0) return 'Positions must be >= 0.';\n\t\tif (from > to) return `\"from\" (${from}) must not be after \"to\" (${to}).`;\n\t\treturn null;\n\t};\n\n\tswitch (command.type) {\n\t\tcase 'canvas:add_node':\n\t\t\tif (command.node.id && known.has(command.node.id)) return `A node with id \"${command.node.id}\" already exists.`;\n\t\t\treturn command.nearNodeId ? missing([command.nearNodeId]) : null;\n\t\tcase 'canvas:remove_nodes':\n\t\tcase 'canvas:highlight':\n\t\t\treturn command.nodeIds.length ? missing(command.nodeIds) : 'Pass at least one node id.';\n\t\tcase 'canvas:select':\n\t\t\treturn missing(command.nodeIds);\n\t\tcase 'canvas:connect':\n\t\t\tif (command.source === command.target) return 'An edge needs two different nodes.';\n\t\t\treturn missing([command.source, command.target]);\n\t\tcase 'canvas:update_node':\n\t\t\treturn missing([command.id]);\n\t\tcase 'prompt:insert_text':\n\t\t\treturn command.at !== undefined ? range(command.at, command.at) : null;\n\t\tcase 'prompt:replace_range':\n\t\tcase 'prompt:select':\n\t\t\treturn range(command.from, command.to);\n\t\tcase 'choreography:say':\n\t\t\treturn command.atNodeId ? missing([command.atNodeId]) : null;\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Plans the steps for an MCP tool call: static editor tools are planned like\n * the equivalent commands, any other tool becomes a `step:tool_call` to be\n * executed with `ToolRegistry.callTool`. Returns `[]` for invalid arguments.\n */\nexport function planToolCall<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tname: string,\n\targs: Record<string, any>,\n\tsnapshot: EditorSnapshot<TNode, TEdge>,\n\toptions?: Parameters<typeof planAgentCommand>[2]\n): AgentStep<TNode, TEdge>[] {\n\tconst command = toolCallToAgentCommand<TNode, TEdge>(name, args);\n\treturn command ? planAgentCommand(command, snapshot, options) : [];\n}\n","import type { Editor } from '@tiptap/core';\nimport { getEditorText } from '../serializers/plain-text.js';\nimport { getEditorMarkdown } from '../serializers/markdown.js';\nimport { TextSelection } from '@tiptap/pm/state';\nimport {\n\tTABLE_ACTIONS,\n\tgetTableActionState,\n\tgetTableState,\n\tisTableEnabled,\n\trunTableAction,\n\ttype TableAction\n} from '../extensions/table/actions.js';\nimport { applyReplacements, searchDocument, type FindMatch } from '../extensions/find-replace/search.js';\nimport type { FindOptions } from '../extensions/find-replace/pattern.js';\nimport type { SpreadsheetController } from '../spreadsheet/controller.js';\nimport { colNameToIndex, indexToColName, isFormulaError } from '../spreadsheet/formula/evaluator.js';\nimport { isSingleCellRange, normalizeRange, rangeContainsRange, rangesIntersect } from '../spreadsheet/table.js';\nimport type { CellRange } from '../spreadsheet/types.js';\nimport { planAgentCommand, toolCallToAgentCommand, validateAgentCommand, type planToolCall } from './planner.js';\nimport type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport {\n\tEDIT_OPERATION_REQUIRED_ARGUMENTS,\n\tEDITOR_CANVAS_TOOLS,\n\tEDITOR_PROMPT_TOOLS,\n\tMCP_TOOL_EDIT_CANVAS,\n\tMCP_TOOL_EDIT_PROMPT,\n\tMCP_TOOL_GET_EDITOR_STATE,\n\tMCP_TOOL_VIEW_EDITOR\n} from '../tools/definitions.js';\nimport type { AgentStep, EditorSnapshot } from './types.js';\nimport { createDocumentChartTools } from './documentChartTools.js';\nimport type { CanvasEdge, CanvasNode } from '../canvas/types.js';\nimport {\n\tDEFAULT_AGENT_CARET_COLOR,\n\tDEFAULT_AGENT_CARET_NAME,\n\tclearAgentCaret,\n\tmarkAgentChanges,\n\treplaceWithTextTransaction,\n\tsetAgentCaret,\n\ttypeAgentText,\n\ttype AgentTypingOptions\n} from '../extensions/agent-caret.js';\n\nexport interface ToolProviderOptions {\n\t/** Provider id (unique per registry). */\n\tid: string;\n\t/** Always prefix tool names with `<namespace>__`. */\n\tnamespace?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Rich-text editor provider\n// ---------------------------------------------------------------------------\n\n/** A TipTap editor, or anything exposing one as `.editor` (e.g. `EditorController`). */\nexport type RichTextToolTarget = Editor | { editor: Editor };\n\nconst toEditor = (target: RichTextToolTarget): Editor =>\n\t'commands' in target ? (target as Editor) : (target as { editor: Editor }).editor;\n\nfunction selectionInfo(editor: Editor) {\n\tconst { from, to } = editor.state.selection;\n\treturn { from, to, text: editor.state.doc.textBetween(from, to, '\\n') };\n}\n\n/** Throws a tool error unless `from..to` is an ordered range inside the document. */\nfunction checkRange(editor: Editor, from: number, to: number) {\n\tconst size = editor.state.doc.content.size;\n\tif (from > to) throw new Error(`\"from\" (${from}) must not be after \"to\" (${to}).`);\n\tif (to > size) throw new Error(`Position ${to} is outside the document (size ${size}).`);\n}\n\n/** Textblocks (paragraphs, headings, cells...) with the positions their text spans. */\nfunction textBlocks(editor: Editor) {\n\tconst blocks: Array<{ type: string; from: number; to: number; text: string }> = [];\n\teditor.state.doc.descendants((node, pos) => {\n\t\tif (!node.isTextblock) return true;\n\t\tblocks.push({ type: node.type.name, from: pos + 1, to: pos + 1 + node.content.size, text: node.textContent });\n\t\treturn false;\n\t});\n\treturn blocks;\n}\n\n/** How the rich-text tools show the agent at work in the document. */\nexport interface RichTextAgentPresence {\n\t/** Caret id (default: the provider id). */\n\tid?: string;\n\t/** Name on the caret's tag (default \"Agent\"). */\n\tname?: string;\n\t/** CSS colour of the caret and its highlights (default violet). */\n\tcolor?: string;\n\t/**\n\t * Type inserted and replacement text out a few characters at a time (still one undo step).\n\t * Default false: edits land at once. `true` uses the default speed.\n\t */\n\ttyping?: boolean | AgentTypingOptions;\n\t/** Hide the caret this long after the last tool call, in ms (default 12000; 0 keeps it). */\n\tidleMs?: number;\n\t/** Keep changed text tinted this long, in ms (default 1500; 0 keeps the tint until the next edit). */\n\tchangedMs?: number;\n}\n\nexport interface RichTextToolProviderOptions extends ToolProviderOptions {\n\t/**\n\t * Draw the agent's caret in the document where it reads and writes, as collaborators' carets\n\t * look in Google Docs (default true). `false` leaves the document untouched by presence.\n\t */\n\tpresence?: boolean | RichTextAgentPresence;\n}\n\n/** Shows the agent's caret, selection and changes in a rich-text editor for the tools. */\nfunction createTextPresence(editor: () => Editor, providerId: string, option: RichTextToolProviderOptions['presence']) {\n\tconst config: RichTextAgentPresence | null = option === false ? null : option === true || option === undefined ? {} : option;\n\tconst caret = {\n\t\tid: config?.id ?? providerId,\n\t\tname: config?.name ?? DEFAULT_AGENT_CARET_NAME,\n\t\tcolor: config?.color ?? DEFAULT_AGENT_CARET_COLOR\n\t};\n\tconst typing: AgentTypingOptions | null = !config?.typing ? null : config.typing === true ? {} : config.typing;\n\tconst idleMs = config?.idleMs ?? 12000;\n\tconst changedMs = config?.changedMs ?? 1500;\n\tlet idleTimer: ReturnType<typeof setTimeout> | undefined;\n\tlet tintTimer: ReturnType<typeof setTimeout> | undefined;\n\n\tconst live = () => {\n\t\tconst ed = editor();\n\t\treturn config && ed && !ed.isDestroyed ? ed : null;\n\t};\n\tconst touch = () => {\n\t\tclearTimeout(idleTimer);\n\t\tif (idleMs > 0) idleTimer = setTimeout(() => live() && clearAgentCaret(live()!, caret.id), idleMs);\n\t};\n\tconst tint = (ed: Editor, ranges: Array<{ from: number; to: number }>) => {\n\t\tclearTimeout(tintTimer);\n\t\tmarkAgentChanges(ed, caret.id, ranges);\n\t\tif (changedMs > 0 && ranges.length) tintTimer = setTimeout(() => live() && markAgentChanges(live()!, caret.id, []), changedMs);\n\t};\n\n\treturn {\n\t\t/** Puts the caret at `head`, selecting from `anchor`. */\n\t\tshow(anchor: number, head = anchor) {\n\t\t\tconst ed = live();\n\t\t\tif (!ed) return;\n\t\t\tsetAgentCaret(ed, { ...caret, from: anchor, to: head });\n\t\t\ttouch();\n\t\t},\n\t\t/** Shows the caret after ranges just changed by a tool, tinting them. */\n\t\tchanged(ranges: Array<{ from: number; to: number }>) {\n\t\t\tconst ed = live();\n\t\t\tif (!ed || !ranges.length) return;\n\t\t\tconst last = ranges[ranges.length - 1];\n\t\t\tsetAgentCaret(ed, { ...caret, from: last.to });\n\t\t\ttint(ed, ranges);\n\t\t\ttouch();\n\t\t},\n\t\t/** Replaces `from..to` with literal text, typed out when typing is on. Returns the inserted range. */\n\t\tasync write(ed: Editor, from: number, to: number, text: string, signal?: AbortSignal) {\n\t\t\tif (live() && typing) {\n\t\t\t\tclearTimeout(tintTimer);\n\t\t\t\tconst range = await typeAgentText(ed, { ...typing, from, to, text, caret, signal });\n\t\t\t\tif (changedMs > 0) tintTimer = setTimeout(() => live() && markAgentChanges(live()!, caret.id, []), changedMs);\n\t\t\t\ttouch();\n\t\t\t\treturn range;\n\t\t\t}\n\t\t\tconst tr = replaceWithTextTransaction(ed.state, from, to, text);\n\t\t\tconst end = tr.mapping.map(to, 1);\n\t\t\tconst range = { from: Math.min(tr.mapping.map(from, -1), end), to: end };\n\t\t\ted.view.dispatch(tr.scrollIntoView());\n\t\t\tif (range.from < range.to) this.changed([range]);\n\t\t\telse this.show(range.to);\n\t\t\treturn range;\n\t\t},\n\t\tdispose() {\n\t\t\tclearTimeout(idleTimer);\n\t\t\tclearTimeout(tintTimer);\n\t\t}\n\t};\n}\n\nconst FIND_SCHEMA_PROPERTIES = {\n\tmatchCase: { type: 'boolean', description: 'Case-sensitive match (default false).' },\n\twholeWord: { type: 'boolean', description: 'Match whole words only (default false).' },\n\tregex: {\n\t\ttype: 'boolean',\n\t\tdescription: 'Treat the query as a JavaScript regular expression (default false: literal text).'\n\t}\n} as const;\n\nconst CONTEXT_CHARS = 30;\n\n/** Runs a literal (or regex) search; throws a tool error for an invalid pattern. */\nfunction findMatches(\n\teditor: Editor,\n\tquery: string,\n\targs: { matchCase?: boolean; wholeWord?: boolean; regex?: boolean },\n\treplacement?: string\n): { matches: FindMatch[]; truncated: boolean } {\n\tconst options: FindOptions = {\n\t\tmatchCase: !!args.matchCase,\n\t\twholeWord: !!args.wholeWord,\n\t\t...(args.regex ? { regex: true } : { literal: true })\n\t};\n\tconst result = searchDocument(editor.state.doc, query, options, replacement !== undefined ? { replacement } : {});\n\tif (result.error) throw new Error(`Invalid search pattern: ${result.error.message}`);\n\treturn { matches: result.matches, truncated: result.truncated };\n}\n\n/**\n * Tools for a mounted rich-text editor: `get_text` and `find_text` (always),\n * plus `insert_text`, `replace_text`, `replace_range`, `replace_selection` and\n * `set_selection` while the editor is editable, `edit_table` when the\n * schema has tables (`createRichTextPreset`'s `table`), and the chart tools\n * (`list_document_charts`; `insert_chart`, `update_chart_in_document` and\n * `insert_sparkline` while editable) when it has charts (`chart`).\n * Positions are ProseMirror document positions (as in `selection.from`), not\n * plain-text offsets; `find_text` and `get_text {blocks: true}` return them.\n * The provider re-announces its tool list when editability changes and\n * exposes no tools once the editor is destroyed.\n */\nexport function createRichTextToolProvider(\n\ttarget: RichTextToolTarget,\n\toptions: RichTextToolProviderOptions\n): AgentToolProvider {\n\tconst editor = () => toEditor(target);\n\tconst charts = createDocumentChartTools(editor);\n\tconst presence = createTextPresence(editor, options.id, options.presence);\n\n\tconst getText: AgentTool = {\n\t\tname: 'get_text',\n\t\ttitle: 'Get document text',\n\t\tdescription:\n\t\t\t'Returns the document content (plain text by default, or HTML / markdown), the current selection and the document size. ' +\n\t\t\t'Editing tools take document positions, not text offsets: pass `blocks: true` to get every paragraph with the positions ' +\n\t\t\t'its text spans, or use find_text to get the positions of a phrase.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tformat: {\n\t\t\t\t\ttype: 'string',\n\t\t\t\t\tenum: ['text', 'html', 'markdown'],\n\t\t\t\t\tdescription: 'Content format (default: text).'\n\t\t\t\t},\n\t\t\t\tblocks: {\n\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\tdescription: 'Also return `blocks`: each paragraph/heading/cell as {type, from, to, text} (default false).'\n\t\t\t\t}\n\t\t\t},\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Get document text',\n\t\t\treadOnlyHint: true,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ format = 'text', blocks = false }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst content =\n\t\t\t\tformat === 'html' ? ed.getHTML() : format === 'markdown' ? getEditorMarkdown(ed) : getEditorText(ed);\n\t\t\treturn {\n\t\t\t\tformat,\n\t\t\t\tcontent,\n\t\t\t\tselection: selectionInfo(ed),\n\t\t\t\tsize: ed.state.doc.content.size,\n\t\t\t\t...(blocks ? { blocks: textBlocks(ed) } : {})\n\t\t\t};\n\t\t}\n\t};\n\n\tconst insertText: AgentTool = {\n\t\tname: 'insert_text',\n\t\ttitle: 'Insert text',\n\t\tdescription:\n\t\t\t'Inserts literal text (newlines start new paragraphs) at a document position, or at the cursor when `at` is omitted. ' +\n\t\t\t'Returns the inserted `range` {from, to}.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\ttext: { type: 'string', description: 'Text to insert.' },\n\t\t\t\tat: { type: 'integer', minimum: 0, description: 'Document position (default: the selection).' }\n\t\t\t},\n\t\t\trequired: ['text'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Insert text',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: async ({ text, at }, { signal }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst size = ed.state.doc.content.size;\n\t\t\tif (at !== undefined && at > size) {\n\t\t\t\tthrow new Error(`Position ${at} is outside the document (size ${size}).`);\n\t\t\t}\n\t\t\tconst { from, to } = ed.state.selection;\n\t\t\tconst range = await presence.write(ed, at ?? from, at ?? to, text, signal);\n\t\t\treturn { inserted: text.length, range, selection: selectionInfo(ed) };\n\t\t}\n\t};\n\n\tconst findText: AgentTool = {\n\t\tname: 'find_text',\n\t\ttitle: 'Find text',\n\t\tdescription:\n\t\t\t'Finds a phrase (literal by default) and returns each match with its document positions {from, to}, ' +\n\t\t\t'ready for replace_range, insert_text `at` or set_selection, plus surrounding context.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tquery: { type: 'string', minLength: 1, description: 'Text (or regex) to find.' },\n\t\t\t\t...FIND_SCHEMA_PROPERTIES,\n\t\t\t\tmaxResults: { type: 'integer', minimum: 1, maximum: 500, description: 'Matches returned (default 50).' }\n\t\t\t},\n\t\t\trequired: ['query'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Find text',\n\t\t\treadOnlyHint: true,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ query, maxResults = 50, ...flags }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst { doc } = ed.state;\n\t\t\tconst { matches, truncated } = findMatches(ed, query, flags);\n\t\t\tif (matches.length) presence.show(matches[0].from, matches[0].to);\n\t\t\treturn {\n\t\t\t\ttotal: matches.length,\n\t\t\t\ttruncated: truncated || matches.length > maxResults,\n\t\t\t\tmatches: matches.slice(0, maxResults).map((m) => ({\n\t\t\t\t\tfrom: m.from,\n\t\t\t\t\tto: m.to,\n\t\t\t\t\ttext: m.text,\n\t\t\t\t\tbefore: doc.textBetween(Math.max(0, m.from - CONTEXT_CHARS), m.from, '\\n', ' '),\n\t\t\t\t\tafter: doc.textBetween(m.to, Math.min(doc.content.size, m.to + CONTEXT_CHARS), '\\n', ' ')\n\t\t\t\t}))\n\t\t\t};\n\t\t}\n\t};\n\n\tconst replaceText: AgentTool = {\n\t\tname: 'replace_text',\n\t\ttitle: 'Find and replace',\n\t\tdescription:\n\t\t\t'Replaces occurrences of a phrase with literal text in one undoable step, keeping the found text\\'s formatting. ' +\n\t\t\t'Replaces every occurrence by default, or only the n-th with `occurrence`. Fails when nothing matches.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tfind: { type: 'string', minLength: 1, description: 'Text (or regex) to find.' },\n\t\t\t\treplace: { type: 'string', description: 'Replacement text (literal; an empty string deletes the matches).' },\n\t\t\t\t...FIND_SCHEMA_PROPERTIES,\n\t\t\t\toccurrence: {\n\t\t\t\t\ttype: 'integer',\n\t\t\t\t\tminimum: 1,\n\t\t\t\t\tdescription: 'Only replace the n-th match (1-based, in document order). Default: all matches.'\n\t\t\t\t}\n\t\t\t},\n\t\t\trequired: ['find', 'replace'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Find and replace',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ find, replace, occurrence, ...flags }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst { matches } = findMatches(ed, find, flags);\n\t\t\tif (matches.length === 0) throw new Error(`No match for ${JSON.stringify(find)}.`);\n\t\t\tif (occurrence !== undefined && occurrence > matches.length) {\n\t\t\t\tthrow new Error(`Occurrence ${occurrence} requested, but there are only ${matches.length} matches.`);\n\t\t\t}\n\t\t\t// The replacement is literal text: `\\r` / `\\n` split paragraphs / insert line breaks.\n\t\t\tconst chosen = (occurrence !== undefined ? [matches[occurrence - 1]] : matches).map((m) => ({\n\t\t\t\t...m,\n\t\t\t\treplacement: String(replace).replace(/\\r\\n?|\\n/g, '\\r')\n\t\t\t}));\n\t\t\tconst tr = ed.state.tr;\n\t\t\tconst ends = applyReplacements(tr, chosen);\n\t\t\ted.view.dispatch(tr.scrollIntoView());\n\t\t\tconst ranges = chosen.map((m, i) => ({ from: tr.mapping.map(m.from, -1), to: ends[i] }));\n\t\t\tpresence.changed(ranges.filter((r) => r.from < r.to));\n\t\t\treturn {\n\t\t\t\treplaced: chosen.length,\n\t\t\t\ttotal: matches.length,\n\t\t\t\tranges,\n\t\t\t\tselection: selectionInfo(ed)\n\t\t\t};\n\t\t}\n\t};\n\n\tconst replaceRange: AgentTool = {\n\t\tname: 'replace_range',\n\t\ttitle: 'Replace a range',\n\t\tdescription:\n\t\t\t'Replaces the content between two document positions (from get_text `blocks` or find_text) with literal text. ' +\n\t\t\t'from === to inserts; an empty text deletes. Returns the new text\\'s `range` {from, to}.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tfrom: { type: 'integer', minimum: 0, description: 'Start document position.' },\n\t\t\t\tto: { type: 'integer', minimum: 0, description: 'End document position.' },\n\t\t\t\ttext: { type: 'string', description: 'Replacement text (newlines start new paragraphs).' }\n\t\t\t},\n\t\t\trequired: ['from', 'to', 'text'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Replace a range',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: async ({ from, to, text }, { signal }) => {\n\t\t\tconst ed = editor();\n\t\t\tcheckRange(ed, from, to);\n\t\t\tconst range = await presence.write(ed, from, to, text, signal);\n\t\t\treturn { replaced: { from, to }, range, selection: selectionInfo(ed), size: ed.state.doc.content.size };\n\t\t}\n\t};\n\n\tconst setSelection: AgentTool = {\n\t\tname: 'set_selection',\n\t\ttitle: 'Set the selection',\n\t\tdescription: 'Selects the content between two document positions (from === to places the cursor).',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tfrom: { type: 'integer', minimum: 0, description: 'Start document position.' },\n\t\t\t\tto: { type: 'integer', minimum: 0, description: 'End document position (default: from).' }\n\t\t\t},\n\t\t\trequired: ['from'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Set the selection',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ from, to = from }) => {\n\t\t\tconst ed = editor();\n\t\t\tcheckRange(ed, from, to);\n\t\t\tconst { doc } = ed.state;\n\t\t\ted.view.dispatch(ed.state.tr.setSelection(TextSelection.between(doc.resolve(from), doc.resolve(to))));\n\t\t\tconst selection = selectionInfo(ed);\n\t\t\tpresence.show(selection.from, selection.to);\n\t\t\treturn { selection };\n\t\t}\n\t};\n\n\tconst replaceSelection: AgentTool = {\n\t\tname: 'replace_selection',\n\t\ttitle: 'Replace selection',\n\t\tdescription:\n\t\t\t'Replaces the selected content with literal text (an empty string deletes the selection). ' +\n\t\t\t'Returns the new text\\'s `range` {from, to}.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: { text: { type: 'string', description: 'Replacement text.' } },\n\t\t\trequired: ['text'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Replace selection',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: async ({ text }, { signal }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst { from, to } = ed.state.selection;\n\t\t\tconst range = await presence.write(ed, from, to, text, signal);\n\t\t\treturn { replaced: { from, to }, range, selection: selectionInfo(ed) };\n\t\t}\n\t};\n\n\tconst editTable: AgentTool = {\n\t\tname: 'edit_table',\n\t\ttitle: 'Edit tables',\n\t\tdescription:\n\t\t\t'Inserts a table or edits the table at the cursor (or at `at`): add/delete rows and columns, delete the table, ' +\n\t\t\t'merge/split cells, toggle header row/column/cell, move between cells, set cell alignment or background. ' +\n\t\t\t'Returns the table size and the cursor cell afterwards.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\toperation: { type: 'string', enum: TABLE_ACTIONS.map((a) => a.action), description: 'Table operation.' },\n\t\t\t\tat: { type: 'integer', minimum: 0, description: 'Document position to place the cursor first (e.g. inside a cell).' },\n\t\t\t\trows: { type: 'integer', minimum: 1, maximum: 100, description: 'insertTable: rows (default 3).' },\n\t\t\t\tcols: { type: 'integer', minimum: 1, maximum: 50, description: 'insertTable: columns (default 3).' },\n\t\t\t\twithHeaderRow: { type: 'boolean', description: 'insertTable: first row is a header row (default true).' },\n\t\t\t\talign: {\n\t\t\t\t\ttype: ['string', 'null'],\n\t\t\t\t\tenum: ['left', 'center', 'right', 'justify', null],\n\t\t\t\t\tdescription: 'setCellAlignment: alignment, or null to clear.'\n\t\t\t\t},\n\t\t\t\tcolor: { type: ['string', 'null'], description: 'setCellBackground: CSS color, or null to clear.' }\n\t\t\t},\n\t\t\trequired: ['operation'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Edit tables',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ operation, at, ...params }) => {\n\t\t\tconst ed = editor();\n\t\t\tif (at !== undefined) {\n\t\t\t\tconst size = ed.state.doc.content.size;\n\t\t\t\tif (at > size) throw new Error(`Position ${at} is outside the document (size ${size}).`);\n\t\t\t\ted.view.dispatch(ed.state.tr.setSelection(TextSelection.near(ed.state.doc.resolve(at))));\n\t\t\t}\n\t\t\tconst action = operation as TableAction;\n\t\t\tconst state = getTableActionState(ed, action, params);\n\t\t\tif (state !== true) throw new Error(state);\n\t\t\trunTableAction(ed, action, params);\n\t\t\tconst table = getTableState(ed);\n\t\t\treturn {\n\t\t\t\toperation,\n\t\t\t\ttable: table.inTable\n\t\t\t\t\t? { rows: table.rows, columns: table.columns, row: table.row, column: table.column }\n\t\t\t\t\t: null,\n\t\t\t\tselection: selectionInfo(ed)\n\t\t\t};\n\t\t}\n\t};\n\n\treturn {\n\t\tid: options.id,\n\t\tnamespace: options.namespace,\n\t\ttools: () => {\n\t\t\tconst ed = editor();\n\t\t\tif (!ed || ed.isDestroyed) return [];\n\t\t\tif (!ed.isEditable) return [getText, findText, ...charts.read()];\n\t\t\tconst tools = [getText, findText, insertText, replaceText, replaceRange, replaceSelection, setSelection];\n\t\t\treturn [...tools, ...(isTableEnabled(ed) ? [editTable] : []), ...charts.read(), ...charts.write()];\n\t\t},\n\t\tsubscribe: (notify) => {\n\t\t\tconst ed = editor();\n\t\t\t// `setEditable` emits `update`; the registry ignores signals that don't change the list.\n\t\t\ted.on('update', notify);\n\t\t\ted.on('destroy', notify);\n\t\t\treturn () => {\n\t\t\t\ted.off('update', notify);\n\t\t\t\ted.off('destroy', notify);\n\t\t\t\tpresence.dispose();\n\t\t\t};\n\t\t}\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Spreadsheet provider\n// ---------------------------------------------------------------------------\n\nconst CELL_REF_REGEX = /^\\$?([A-Za-z]{1,3})\\$?(\\d{1,7})$/;\n\nfunction parseCell(ref: string): { col: number; row: number } {\n\tconst match = CELL_REF_REGEX.exec(ref.trim());\n\tif (!match) throw new Error(`Invalid cell reference \"${ref}\". Use A1 notation, e.g. \"B3\".`);\n\treturn { col: colNameToIndex(match[1].toUpperCase()), row: parseInt(match[2], 10) - 1 };\n}\n\nconst COLUMN_REGEX = /^[A-Za-z]{1,3}$/;\n\nfunction parseColumn(name: string): number {\n\tif (!COLUMN_REGEX.test(name.trim())) throw new Error(`Invalid column \"${name}\". Use a letter name, e.g. \"C\".`);\n\treturn colNameToIndex(name.trim().toUpperCase());\n}\n\nfunction parseRange(ref: string): CellRange {\n\tconst [startRef, endRef = startRef, extra] = String(ref).split(':');\n\tif (extra !== undefined) throw new Error(`Invalid range \"${ref}\". Use A1 notation, e.g. \"A1:C5\".`);\n\tconst start = parseCell(startRef);\n\tconst end = parseCell(endRef);\n\treturn normalizeRange({ startRow: start.row, startCol: start.col, endRow: end.row, endCol: end.col });\n}\n\nconst formatRange = (r: CellRange) =>\n\t`${indexToColName(r.startCol)}${r.startRow + 1}:${indexToColName(r.endCol)}${r.endRow + 1}`;\n\nconst CELL_INPUT = { type: ['string', 'number', 'boolean', 'null'] } as const;\n\n/**\n * Tools for a spreadsheet controller: `get_range` (A1 notation, values and raw\n * input), `set_cell` and `set_range` (raw input; values starting with `=` are\n * formulas; `set_range` writes a whole block as one undo step), and\n * `insert_rows` / `delete_rows` / `insert_columns` / `delete_columns`\n * (formula references shift like a user edit), and `merge_cells` /\n * `unmerge_cells` / `transpose_range` on an A1 range (not the user's\n * selection), checked against merges and read-only cells first.\n */\nexport function createSpreadsheetToolProvider(\n\tcontroller: SpreadsheetController,\n\toptions: ToolProviderOptions\n): AgentToolProvider {\n\tconst lookup = (row: number, col: number) => {\n\t\tconst rowDef = controller.document.rows[row];\n\t\tconst colDef = controller.document.columns[col];\n\t\treturn rowDef && colDef ? { rowDef, colDef } : null;\n\t};\n\n\tconst getRange: AgentTool = {\n\t\tname: 'get_range',\n\t\ttitle: 'Read cells',\n\t\tdescription:\n\t\t\t'Reads a cell range in A1 notation (e.g. \"A1:C5\" or \"B2\") and returns computed values, raw input, errors and the merged ranges that touch it (a merged range shows its value in its top-left cell).',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\trange: { type: 'string', description: 'A1-notation cell or range.' }\n\t\t\t},\n\t\t\trequired: ['range'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Read cells',\n\t\t\treadOnlyHint: true,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ range }) => {\n\t\t\tconst [startRef, endRef = startRef] = String(range).split(':');\n\t\t\tconst start = parseCell(startRef);\n\t\t\tconst end = parseCell(endRef);\n\t\t\tconst rowCount = controller.document.rows.length;\n\t\t\tconst colCount = controller.document.columns.length;\n\t\t\tconst r0 = Math.min(start.row, end.row);\n\t\t\tconst r1 = Math.min(Math.max(start.row, end.row), rowCount - 1);\n\t\t\tconst c0 = Math.min(start.col, end.col);\n\t\t\tconst c1 = Math.min(Math.max(start.col, end.col), colCount - 1);\n\t\t\tconst values: unknown[][] = [];\n\t\t\tconst raw: string[][] = [];\n\t\t\tconst errors: Array<{ cell: string; error: string }> = [];\n\t\t\tfor (let r = r0; r <= r1; r++) {\n\t\t\t\tconst valueRow: unknown[] = [];\n\t\t\t\tconst rawRow: string[] = [];\n\t\t\t\tfor (let c = c0; c <= c1; c++) {\n\t\t\t\t\tconst found = lookup(r, c)!;\n\t\t\t\t\tconst cell = controller.getCell(found.rowDef.id, found.colDef.id);\n\t\t\t\t\tvalueRow.push(cell.error ?? cell.value);\n\t\t\t\t\trawRow.push(cell.raw);\n\t\t\t\t\tconst error = cell.error ?? (isFormulaError(cell.value) ? cell.value : undefined);\n\t\t\t\t\tif (error) errors.push({ cell: `${indexToColName(c)}${r + 1}`, error });\n\t\t\t\t}\n\t\t\t\tvalues.push(valueRow);\n\t\t\t\traw.push(rawRow);\n\t\t\t}\n\t\t\tconst read: CellRange = { startRow: r0, startCol: c0, endRow: r1, endCol: c1 };\n\t\t\treturn {\n\t\t\t\trange: values.length && values[0].length ? formatRange(read) : null,\n\t\t\t\tvalues,\n\t\t\t\traw,\n\t\t\t\terrors,\n\t\t\t\tmerges: controller.getMerges().filter((m) => rangesIntersect(m, read)).map(formatRange),\n\t\t\t\tsheet: { rows: rowCount, columns: colCount }\n\t\t\t};\n\t\t}\n\t};\n\n\tconst setCell: AgentTool = {\n\t\tname: 'set_cell',\n\t\ttitle: 'Set a cell',\n\t\tdescription: 'Sets the raw input of one cell (A1 notation). Values starting with \"=\" are formulas.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tcell: { type: 'string', description: 'A1-notation cell, e.g. \"B3\".' },\n\t\t\t\tvalue: { type: ['string', 'number', 'boolean'], description: 'Raw cell input.' }\n\t\t\t},\n\t\t\trequired: ['cell', 'value'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Set a cell',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ cell, value }) => {\n\t\t\tconst { row, col } = parseCell(cell);\n\t\t\tconst found = lookup(row, col);\n\t\t\tif (!found) throw new Error(`Cell ${cell} is outside the sheet.`);\n\t\t\tconst { rowDef, colDef } = found;\n\t\t\tif (colDef.readOnly || controller.getCell(rowDef.id, colDef.id).readOnly || colDef.type === 'formula') {\n\t\t\t\tthrow new Error(`Cell ${cell} is read-only.`);\n\t\t\t}\n\t\t\tcontroller.setCellValue(rowDef.id, colDef.id, String(value));\n\t\t\tconst updated = controller.getCell(rowDef.id, colDef.id);\n\t\t\treturn { cell: cell.toUpperCase(), raw: updated.raw, value: updated.error ?? updated.value };\n\t\t}\n\t};\n\n\tconst isReadOnly = (row: number, col: number) => {\n\t\tconst found = lookup(row, col);\n\t\tif (!found) return false;\n\t\tconst { rowDef, colDef } = found;\n\t\treturn !!(colDef.readOnly || colDef.type === 'formula' || controller.getCell(rowDef.id, colDef.id).readOnly);\n\t};\n\n\tconst setRange: AgentTool = {\n\t\tname: 'set_range',\n\t\ttitle: 'Set a block of cells',\n\t\tdescription:\n\t\t\t'Writes a 2D block of raw inputs starting at a cell (A1 notation), row by row, as one undo step. ' +\n\t\t\t'Values starting with \"=\" are formulas; null or \"\" clears a cell. The sheet grows to fit. Fails without writing ' +\n\t\t\t'anything if any target cell is read-only.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tstart: { type: 'string', description: 'Top-left cell, e.g. \"A1\".' },\n\t\t\t\tvalues: {\n\t\t\t\t\ttype: 'array',\n\t\t\t\t\tminItems: 1,\n\t\t\t\t\tmaxItems: 10000,\n\t\t\t\t\titems: { type: 'array', items: CELL_INPUT, maxItems: 1000 },\n\t\t\t\t\tdescription: 'Rows of raw cell inputs, e.g. [[\"Name\", \"Total\"], [\"A\", \"=B3*2\"]].'\n\t\t\t\t}\n\t\t\t},\n\t\t\trequired: ['start', 'values'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Set a block of cells',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ start, values }) => {\n\t\t\tconst { row, col } = parseCell(start);\n\t\t\tconst rows = values as Array<Array<string | number | boolean | null>>;\n\t\t\tconst readOnly: string[] = [];\n\t\t\trows.forEach((line, r) =>\n\t\t\t\tline.forEach((_, c) => {\n\t\t\t\t\tif (isReadOnly(row + r, col + c)) readOnly.push(`${indexToColName(col + c)}${row + r + 1}`);\n\t\t\t\t})\n\t\t\t);\n\t\t\tif (readOnly.length) throw new Error(`Read-only cells: ${readOnly.slice(0, 20).join(', ')}. Nothing was written.`);\n\t\t\tcontroller.importRows(\n\t\t\t\trows.map((line) => line.map((v) => (v === null ? '' : String(v)))),\n\t\t\t\t{ startRow: row, startCol: col }\n\t\t\t);\n\t\t\tconst width = Math.max(0, ...rows.map((line) => line.length));\n\t\t\tconst end = `${indexToColName(col + Math.max(width, 1) - 1)}${row + rows.length}`;\n\t\t\tconst errors: Array<{ cell: string; error: string }> = [];\n\t\t\trows.forEach((line, r) =>\n\t\t\t\tline.forEach((_, c) => {\n\t\t\t\t\tconst found = lookup(row + r, col + c);\n\t\t\t\t\tif (!found) return;\n\t\t\t\t\tconst cell = controller.getCell(found.rowDef.id, found.colDef.id);\n\t\t\t\t\tconst error = cell.error ?? (isFormulaError(cell.value) ? cell.value : undefined);\n\t\t\t\t\tif (error) errors.push({ cell: `${indexToColName(col + c)}${row + r + 1}`, error: String(error) });\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn {\n\t\t\t\trange: `${indexToColName(col)}${row + 1}:${end}`,\n\t\t\t\twritten: rows.reduce((n, line) => n + line.length, 0),\n\t\t\t\terrors,\n\t\t\t\tsheet: { rows: controller.document.rows.length, columns: controller.document.columns.length }\n\t\t\t};\n\t\t}\n\t};\n\n\tconst COUNT = { type: 'integer', minimum: 1, maximum: 1000, description: 'How many (default 1).' } as const;\n\tconst sheetSize = () => ({ rows: controller.document.rows.length, columns: controller.document.columns.length });\n\tconst structural = (\n\t\tname: string,\n\t\ttitle: string,\n\t\tdescription: string,\n\t\tproperties: Record<string, unknown>,\n\t\trequired: string[],\n\t\tdestructive: boolean,\n\t\thandler: AgentTool['handler']\n\t): AgentTool => ({\n\t\tname,\n\t\ttitle,\n\t\tdescription,\n\t\tinputSchema: { type: 'object', properties: properties as never, required, additionalProperties: false },\n\t\tannotations: { title, readOnlyHint: false, destructiveHint: destructive, idempotentHint: false, openWorldHint: false },\n\t\thandler\n\t});\n\n\tconst insertRows = structural(\n\t\t'insert_rows',\n\t\t'Insert rows',\n\t\t'Inserts empty rows before a row number (1-based; one past the last row appends). References below shift down.',\n\t\t{ before: { type: 'integer', minimum: 1, description: 'Row number to insert before.' }, count: COUNT },\n\t\t['before'],\n\t\tfalse,\n\t\t({ before, count = 1 }) => {\n\t\t\tconst index = before - 1;\n\t\t\tif (index > controller.document.rows.length) throw new Error(`Row ${before} is past the end of the sheet (${controller.document.rows.length} rows).`);\n\t\t\tfor (let i = 0; i < count; i++) controller.insertRow(index);\n\t\t\treturn { inserted: count, rows: `${before}:${before + count - 1}`, sheet: sheetSize() };\n\t\t}\n\t);\n\tconst deleteRows = structural(\n\t\t'delete_rows',\n\t\t'Delete rows',\n\t\t'Deletes rows starting at a row number (1-based) with their contents. References below shift up; references to deleted cells become #REF!.',\n\t\t{ start: { type: 'integer', minimum: 1, description: 'First row number to delete.' }, count: COUNT },\n\t\t['start'],\n\t\ttrue,\n\t\t({ start, count = 1 }) => {\n\t\t\tconst rows = controller.document.rows.slice(start - 1, start - 1 + count);\n\t\t\tif (rows.length < count) throw new Error(`Rows ${start}-${start + count - 1} are not all in the sheet (${controller.document.rows.length} rows).`);\n\t\t\tfor (const row of rows) controller.deleteRow(row.id);\n\t\t\treturn { deleted: count, sheet: sheetSize() };\n\t\t}\n\t);\n\tconst insertColumns = structural(\n\t\t'insert_columns',\n\t\t'Insert columns',\n\t\t'Inserts empty columns before a column letter (one past the last column appends). References to the right shift.',\n\t\t{ before: { type: 'string', description: 'Column letter to insert before, e.g. \"C\".' }, count: COUNT },\n\t\t['before'],\n\t\tfalse,\n\t\t({ before, count = 1 }) => {\n\t\t\tconst index = parseColumn(before);\n\t\t\tif (index > controller.document.columns.length) throw new Error(`Column ${before} is past the end of the sheet (${controller.document.columns.length} columns).`);\n\t\t\tfor (let i = 0; i < count; i++) controller.insertColumn(index);\n\t\t\treturn { inserted: count, columns: `${indexToColName(index)}:${indexToColName(index + count - 1)}`, sheet: sheetSize() };\n\t\t}\n\t);\n\tconst deleteColumns = structural(\n\t\t'delete_columns',\n\t\t'Delete columns',\n\t\t'Deletes columns starting at a column letter with their contents. References to the right shift left; references to deleted cells become #REF!.',\n\t\t{ start: { type: 'string', description: 'First column letter to delete, e.g. \"C\".' }, count: COUNT },\n\t\t['start'],\n\t\ttrue,\n\t\t({ start, count = 1 }) => {\n\t\t\tconst index = parseColumn(start);\n\t\t\tconst columns = controller.document.columns.slice(index, index + count);\n\t\t\tif (columns.length < count) throw new Error(`Columns from ${start} (${count}) are not all in the sheet (${controller.document.columns.length} columns).`);\n\t\t\tfor (const column of columns) controller.deleteColumn(column.id);\n\t\t\treturn { deleted: count, sheet: sheetSize() };\n\t\t}\n\t);\n\n\tconst RANGE = { type: 'string', description: 'A1-notation range of at least two cells, e.g. \"A1:C1\".' } as const;\n\tconst inSheet = (r: CellRange, ref: string) => {\n\t\tif (r.endRow >= controller.document.rows.length || r.endCol >= controller.document.columns.length) {\n\t\t\tthrow new Error(`Range ${ref} is outside the sheet (${controller.document.rows.length} rows, ${controller.document.columns.length} columns).`);\n\t\t}\n\t};\n\tconst readOnlyIn = (r: CellRange) => {\n\t\tconst cells: string[] = [];\n\t\tfor (let row = r.startRow; row <= r.endRow; row++) {\n\t\t\tfor (let col = r.startCol; col <= r.endCol; col++) if (isReadOnly(row, col)) cells.push(`${indexToColName(col)}${row + 1}`);\n\t\t}\n\t\treturn cells;\n\t};\n\tconst SPILL = 'it overlaps the spill range of a dynamic array formula; move or clear that formula first';\n\n\tconst mergeCells = structural(\n\t\t'merge_cells',\n\t\t'Merge cells',\n\t\t'Merges a range into one cell showing the top-left cell\\'s value. The other cells in the range are cleared, so move their contents first. ' +\n\t\t\t'Merges fully inside the range are absorbed. One undo step. Returns the merged range and the cells that were cleared. Use merges only for titles outside data regions: they break sorting, filtering and transposing.',\n\t\t{ range: RANGE },\n\t\t['range'],\n\t\ttrue,\n\t\t({ range }) => {\n\t\t\tconst r = parseRange(range);\n\t\t\tinSheet(r, range);\n\t\t\tif (isSingleCellRange(r)) throw new Error(`${range} is a single cell; give a range of at least two cells.`);\n\t\t\tconst partial = controller.getMerges().find((m) => rangesIntersect(m, r) && !rangeContainsRange(r, m));\n\t\t\tif (partial) throw new Error(`${range} partly overlaps the merged range ${formatRange(partial)}; include all of it or unmerge it first.`);\n\t\t\tconst readOnly = readOnlyIn(r);\n\t\t\tif (readOnly.length) throw new Error(`Read-only cells: ${readOnly.slice(0, 20).join(', ')}. Nothing was merged.`);\n\t\t\tconst cleared: string[] = [];\n\t\t\tfor (let row = r.startRow; row <= r.endRow; row++) {\n\t\t\t\tfor (let col = r.startCol; col <= r.endCol; col++) {\n\t\t\t\t\tif (row === r.startRow && col === r.startCol) continue;\n\t\t\t\t\tconst { rowDef, colDef } = lookup(row, col)!;\n\t\t\t\t\tif (controller.getCell(rowDef.id, colDef.id).raw !== '') cleared.push(`${indexToColName(col)}${row + 1}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!controller.mergeCells(r)) throw new Error(`Cannot merge ${range}: ${SPILL}.`);\n\t\t\treturn { merged: formatRange(r), cleared };\n\t\t}\n\t);\n\tconst unmergeCells = structural(\n\t\t'unmerge_cells',\n\t\t'Unmerge cells',\n\t\t'Splits every merged range that touches a range back into separate cells (the value stays in the top-left cell; the others are empty). One undo step. Returns the ranges that were unmerged.',\n\t\t{ range: { type: 'string', description: 'A1-notation cell or range; every merge touching it is split, e.g. \"A1\".' } },\n\t\t['range'],\n\t\tfalse,\n\t\t({ range }) => {\n\t\t\tconst r = parseRange(range);\n\t\t\tconst touched = controller.getMerges().filter((m) => rangesIntersect(m, r));\n\t\t\tif (!touched.length) throw new Error(`No merged cells in ${range}. get_range lists the merges in a range.`);\n\t\t\tcontroller.unmergeCells(r);\n\t\t\treturn { unmerged: touched.map(formatRange) };\n\t\t}\n\t);\n\tconst transposeRange = structural(\n\t\t'transpose_range',\n\t\t'Transpose a range',\n\t\t'Swaps the rows and columns of a range in place, anchored on its top-left cell (A1:C2 becomes A1:B3), moving values, formulas, formats and rules; ' +\n\t\t\t'formulas that point into the range follow their cells. The sheet grows to fit. One undo step. Returns the new range. Cells the result covers outside the original range are overwritten.',\n\t\t{ range: RANGE },\n\t\t['range'],\n\t\ttrue,\n\t\t({ range }) => {\n\t\t\tconst s = parseRange(range);\n\t\t\tinSheet(s, range);\n\t\t\tif (isSingleCellRange(s)) throw new Error(`${range} is a single cell; give a range of at least two cells.`);\n\t\t\tconst t: CellRange = {\n\t\t\t\tstartRow: s.startRow,\n\t\t\t\tstartCol: s.startCol,\n\t\t\t\tendRow: s.startRow + (s.endCol - s.startCol),\n\t\t\t\tendCol: s.startCol + (s.endRow - s.startRow)\n\t\t\t};\n\t\t\tconst merge = controller.getMerges().find((m) => rangesIntersect(m, s) || rangesIntersect(m, t));\n\t\t\tif (merge) throw new Error(`Cannot transpose ${range}: the merged range ${formatRange(merge)} is in the way; unmerge it first.`);\n\t\t\tconst readOnly = [...new Set([...readOnlyIn(s), ...readOnlyIn(t)])];\n\t\t\tif (readOnly.length) throw new Error(`Read-only cells: ${readOnly.slice(0, 20).join(', ')}. Nothing was transposed.`);\n\t\t\tif (!controller.transposeRange(s)) throw new Error(`Cannot transpose ${range}: ${SPILL}.`);\n\t\t\treturn { range: formatRange(t), sheet: sheetSize() };\n\t\t}\n\t);\n\n\treturn {\n\t\tid: options.id,\n\t\tnamespace: options.namespace,\n\t\ttools: [getRange, setCell, setRange, insertRows, deleteRows, insertColumns, deleteColumns, mergeCells, unmergeCells, transposeRange]\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Static editor tools (tools.ts) as a provider\n// ---------------------------------------------------------------------------\n\nexport interface EditorMcpToolProviderOptions<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n> extends ToolProviderOptions {\n\t/** Current editor / canvas state. */\n\tgetSnapshot: () => EditorSnapshot<TNode, TEdge>;\n\t/** Applies planned steps (e.g. via your canvas runes / editor controller). */\n\tapplySteps: (steps: AgentStep<TNode, TEdge>[]) => void | Promise<void>;\n\t/** Optional `view_editor` implementation; the tool is only exposed when provided. */\n\tcaptureView?: (args: { scope?: 'viewport' | 'canvas'; format?: 'jpeg' | 'png'; maxEdge?: number }) =>\n\t\t| Promise<{ data: string; mimeType: string }>\n\t\t| { data: string; mimeType: string };\n\t/** Options forwarded to the planner. */\n\tplannerOptions?: Parameters<typeof planToolCall>[3];\n\t/**\n\t * `split` (default): one strictly typed tool per operation (`canvas_add_node`,\n\t * `canvas_connect`, ..., `prompt_insert_text`, ...). `combined`: the\n\t * `edit_canvas` / `edit_prompt` tools with an `operation` argument.\n\t */\n\ttoolStyle?: 'split' | 'combined';\n\t/** Which edit tools to expose (default both). */\n\tsurfaces?: { canvas?: boolean; prompt?: boolean };\n}\n\n/** Describes which arguments a combined `edit_*` call is missing. */\nfunction missingArgumentsMessage(name: 'edit_canvas' | 'edit_prompt', args: Record<string, any>): string {\n\tconst required = EDIT_OPERATION_REQUIRED_ARGUMENTS[name][args.operation as string];\n\tif (!required) return `Unknown operation \"${args.operation}\".`;\n\tconst missing = required.filter((key) =>\n\t\tkey === 'id' ? args.id === undefined && args.node?.id === undefined : args[key] === undefined\n\t);\n\treturn missing.length\n\t\t? `Operation \"${args.operation}\" requires ${required.join(', ')}; missing: ${missing.join(', ')}.`\n\t\t: `Operation \"${args.operation}\" has invalid arguments (expected ${required.join(', ')} with the types in the schema).`;\n}\n\n/** What a set of applied steps created, so the agent can refer to it next. */\nfunction describeSteps<TNode extends CanvasNode, TEdge extends CanvasEdge>(steps: AgentStep<TNode, TEdge>[]) {\n\tconst nodes: Array<{ id: string; position: { x: number; y: number } }> = [];\n\tconst edges: Array<{ id: string; source: string; target: string }> = [];\n\tfor (const step of steps) {\n\t\tif (step.type === 'step:canvas_add_node') nodes.push({ id: step.node.id, position: step.node.position });\n\t\tif (step.type === 'step:canvas_add_edge') edges.push({ id: step.edge.id, source: step.edge.source, target: step.edge.target });\n\t}\n\treturn {\n\t\tapplied: steps.length,\n\t\tsteps: steps.map((s) => s.type),\n\t\t...(nodes.length || edges.length ? { created: { nodes, edges } } : {})\n\t};\n}\n\n/**\n * Exposes the editor MCP tools (`get_editor_state`, the canvas and prompt\n * edit tools, and `view_editor` when `captureView` is given) through a\n * `ToolRegistry`. Edits are checked against the current snapshot\n * (`validateAgentCommand`: unknown node ids, duplicate ids, unordered ranges\n * are tool errors), planned with the planner and handed to `applySteps`.\n * Results list the ids of created nodes and edges.\n */\nexport function createEditorMcpToolProvider<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(options: EditorMcpToolProviderOptions<TNode, TEdge>): AgentToolProvider {\n\tconst edit = (name: string) => async (args: Record<string, any>) => {\n\t\tconst command = toolCallToAgentCommand<TNode, TEdge>(name, args);\n\t\tif (!command) {\n\t\t\tthrow new Error(\n\t\t\t\tname === 'edit_canvas' || name === 'edit_prompt'\n\t\t\t\t\t? missingArgumentsMessage(name, args)\n\t\t\t\t\t: `Invalid arguments for \"${name}\".`\n\t\t\t);\n\t\t}\n\t\tconst snapshot = options.getSnapshot();\n\t\tconst problem = validateAgentCommand(command, snapshot);\n\t\tif (problem) throw new Error(problem);\n\t\tconst steps = planAgentCommand(command, snapshot, options.plannerOptions);\n\t\tawait options.applySteps(steps);\n\t\treturn describeSteps(steps);\n\t};\n\n\tconst canvas = options.surfaces?.canvas ?? true;\n\tconst prompt = options.surfaces?.prompt ?? true;\n\tconst editTools =\n\t\toptions.toolStyle === 'combined'\n\t\t\t? [...(canvas ? [MCP_TOOL_EDIT_CANVAS] : []), ...(prompt ? [MCP_TOOL_EDIT_PROMPT] : [])]\n\t\t\t: [...(canvas ? EDITOR_CANVAS_TOOLS : []), ...(prompt ? EDITOR_PROMPT_TOOLS : [])];\n\n\tconst tools: AgentTool[] = [\n\t\t{\n\t\t\t...MCP_TOOL_GET_EDITOR_STATE,\n\t\t\thandler: ({ includeDocument = true, includeCanvas = true }) => {\n\t\t\t\tconst snapshot = options.getSnapshot();\n\t\t\t\treturn {\n\t\t\t\t\t...(includeDocument && snapshot.document ? { document: snapshot.document } : {}),\n\t\t\t\t\t...(includeCanvas && snapshot.canvas ? { canvas: snapshot.canvas } : {})\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t\t...editTools.map((definition) => ({ ...definition, handler: edit(definition.name) }))\n\t] as AgentTool[];\n\n\tif (options.captureView) {\n\t\tconst capture = options.captureView;\n\t\ttools.push({\n\t\t\t...MCP_TOOL_VIEW_EDITOR,\n\t\t\thandler: async (args) => {\n\t\t\t\tconst image = await capture(args);\n\t\t\t\treturn { content: [{ type: 'image', data: image.data, mimeType: image.mimeType }] };\n\t\t\t}\n\t\t} as AgentTool);\n\t}\n\n\treturn { id: options.id, namespace: options.namespace, tools };\n}\n","import type { Editor } from '@tiptap/core';\nimport { closeHistory } from '@tiptap/pm/history';\nimport type { Node as ProsemirrorNode } from '@tiptap/pm/model';\nimport { getDocumentCharts, type DocumentChart } from '../extensions/chart/chart-node.js';\nimport { MAX_SPARKLINE_VALUES, SPARKLINE_KINDS, normalizeSparklineValues, sparklineLabel } from '../extensions/chart/sparkline-node.js';\nimport { CHART_UPDATE_PROPERTIES, SERIES_PROPS, VALUE } from '../chart/schemas.js';\nimport { normalizeChartSpec, parseChartValue } from '../chart/spec.js';\nimport { describeChart } from '../chart/summary.js';\nimport type { ChartSpec } from '../chart/types.js';\nimport type { AgentTool } from '../tools/registry.js';\nimport type { JsonSchema } from '../tools/schema.js';\n\nconst READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\nconst WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false } as const;\nconst WRITE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\n\nconst SIZE_WIDTH: JsonSchema = {\n\ttype: ['number', 'null'],\n\tminimum: 50,\n\tmaximum: 4096,\n\tdescription: 'Width in pixels, or null to fill the line (default null).'\n};\nconst SIZE_HEIGHT: JsonSchema = { type: 'number', minimum: 50, maximum: 4096, description: 'Height in pixels (default 320).' };\n\n/** The chart data and presentation fields an agent can pass (a `ChartSpec` without `version`). */\nconst SPEC_PROPERTIES: Record<string, JsonSchema> = {\n\t...CHART_UPDATE_PROPERTIES,\n\tcategories: { type: 'array', items: { type: 'string' }, description: 'Category labels in order (x axis labels, or pie slices).' },\n\tseries: {\n\t\ttype: 'array',\n\t\tdescription: 'The series, each with one value per category (padded with gaps).',\n\t\titems: {\n\t\t\ttype: 'object',\n\t\t\tproperties: { id: { type: 'string', description: 'Stable id (default: from the name).' }, ...SERIES_PROPS },\n\t\t\trequired: ['name', 'values'],\n\t\t\tadditionalProperties: false\n\t\t}\n\t}\n};\n\n/** Drops `null` from series fields (null means \"use the default\" in the tool schema). */\nfunction cleanSeries(series: unknown): unknown {\n\tif (!Array.isArray(series)) return series;\n\treturn series.map((s) => (s && typeof s === 'object' ? Object.fromEntries(Object.entries(s).filter(([, v]) => v !== null)) : s));\n}\n\n/** Normalizes agent input as a chart, throwing every problem as one sentence list. */\nfunction checkedSpec(input: Record<string, unknown>): ChartSpec {\n\tconst { spec, issues } = normalizeChartSpec({ ...input, ...(input.series !== undefined ? { series: cleanSeries(input.series) } : {}) });\n\tif (issues.length) throw new Error(`The chart was not changed: ${issues.map((i) => i.message).join(' ')}`);\n\treturn spec;\n}\n\nfunction describe(chart: DocumentChart, includeSpec: boolean) {\n\tconst { spec, node } = chart;\n\treturn {\n\t\tid: chart.id,\n\t\tpos: chart.pos,\n\t\ttype: spec.type,\n\t\ttitle: spec.title,\n\t\tcategories: spec.categories,\n\t\tseries: spec.series.map((s) => ({ id: s.id, name: s.name })),\n\t\twidth: node.attrs.width ?? null,\n\t\theight: node.attrs.height,\n\t\tdescription: describeChart(spec),\n\t\t...(includeSpec ? { spec } : {})\n\t};\n}\n\nfunction sparklines(doc: ProsemirrorNode) {\n\tconst found: { pos: number; kind: string; values: (number | null)[]; label: string }[] = [];\n\tdoc.descendants((node, pos) => {\n\t\tif (node.type.name === 'sparkline') found.push({ pos, kind: node.attrs.kind, values: node.attrs.values, label: sparklineLabel(node) });\n\t});\n\treturn found;\n}\n\nfunction checkPosition(editor: Editor, at: number | undefined) {\n\tconst size = editor.state.doc.content.size;\n\tif (at !== undefined && at > size) throw new Error(`Position ${at} is outside the document (size ${size}).`);\n}\n\n/**\n * Chart tools for a rich-text editor whose schema has the `chart` node:\n * `list_document_charts` (read), `insert_chart` and `update_chart_in_document` (writes), plus\n * `insert_sparkline` when the schema has `sparkline`. `createRichTextToolProvider` adds them.\n */\nexport function createDocumentChartTools(editor: () => Editor): { read: () => AgentTool[]; write: () => AgentTool[] } {\n\tconst chartType = () => editor().schema.nodes.chart;\n\tconst findChart = (args: { chartId?: string; pos?: number }): DocumentChart => {\n\t\tconst charts = getDocumentCharts(editor().state.doc);\n\t\tconst list = charts.map((c) => `\"${c.id}\" at ${c.pos}`).join(', ') || 'none; add one with insert_chart';\n\t\tif (args.chartId !== undefined) {\n\t\t\tconst chart = charts.find((c) => c.id === args.chartId);\n\t\t\tif (!chart) throw new Error(`There is no chart \"${args.chartId}\" in the document. Charts: ${list}.`);\n\t\t\tif (args.pos !== undefined && args.pos !== chart.pos) throw new Error(`Chart \"${args.chartId}\" is at ${chart.pos}, not ${args.pos}; pass one of chartId or pos.`);\n\t\t\treturn chart;\n\t\t}\n\t\tif (args.pos !== undefined) {\n\t\t\tconst chart = charts.find((c) => c.pos === args.pos);\n\t\t\tif (!chart) throw new Error(`There is no chart at position ${args.pos}. Charts: ${list}.`);\n\t\t\treturn chart;\n\t\t}\n\t\tthrow new Error(`Pass chartId or pos (from list_document_charts). Charts: ${list}.`);\n\t};\n\n\tconst listCharts: AgentTool = {\n\t\tname: 'list_document_charts',\n\t\ttitle: 'List charts in the document',\n\t\tdescription:\n\t\t\t'Lists the charts in the document in order: id, document position, type, title, categories, series (ids and names), size in pixels and a plain-language description of the data. Also lists inline sparklines (position, kind, values). Pass the id or position to update_chart_in_document.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: { includeSpec: { type: 'boolean', description: 'Also return each chart\\'s full spec (default false).' } },\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: { title: 'List charts in the document', ...READ },\n\t\thandler: ({ includeSpec = false }) => {\n\t\t\tconst doc = editor().state.doc;\n\t\t\treturn { charts: getDocumentCharts(doc).map((c) => describe(c, includeSpec)), sparklines: editor().schema.nodes.sparkline ? sparklines(doc) : [] };\n\t\t}\n\t};\n\n\tconst insertChart: AgentTool = {\n\t\tname: 'insert_chart',\n\t\ttitle: 'Insert a chart',\n\t\tdescription:\n\t\t\t'Inserts a chart block at a document position (or the cursor), as one undo step. Give the categories and series (one value per category) and any presentation fields (type, titles, axes, legend, stacking, palette, …). Invalid fields change nothing and come back as sentences. Returns the new chart\\'s id, position and stored spec.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tspec: { type: 'object', description: 'The chart: categories and series, plus optional presentation fields.', properties: SPEC_PROPERTIES, required: ['categories', 'series'], additionalProperties: false },\n\t\t\t\tat: { type: 'integer', minimum: 0, description: 'Document position (default: the selection). A position inside a paragraph splits it.' },\n\t\t\t\twidth: SIZE_WIDTH,\n\t\t\t\theight: SIZE_HEIGHT\n\t\t\t},\n\t\t\trequired: ['spec'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: { title: 'Insert a chart', ...WRITE },\n\t\thandler: ({ spec: input, at, width = null, height }) => {\n\t\t\tconst ed = editor();\n\t\t\tcheckPosition(ed, at);\n\t\t\tconst spec = checkedSpec(input);\n\t\t\tconst id = (ed.extensionManager.extensions.find((e) => e.name === 'chart')?.options.generateId as (() => string) | undefined)?.() ?? `chart-${Date.now().toString(36)}`;\n\t\t\tconst node = { type: 'chart', attrs: { id, spec, width, ...(height !== undefined ? { height } : {}) } };\n\t\t\t// Each tool call is its own undo step, however quickly calls follow each other.\n\t\t\tconst chain = ed.chain().command(({ tr }) => (closeHistory(tr), true));\n\t\t\tconst ok = (at !== undefined ? chain.insertContentAt(at, node) : chain.insertContent(node)).run();\n\t\t\tconst chart = getDocumentCharts(ed.state.doc).find((c) => c.id === id);\n\t\t\tif (!ok || !chart) throw new Error(`A chart cannot go at ${at ?? ed.state.selection.from}; pass a position between blocks or inside a paragraph.`);\n\t\t\treturn { id: chart.id, pos: chart.pos, spec: chart.spec, width: chart.node.attrs.width, height: chart.node.attrs.height };\n\t\t}\n\t};\n\n\tconst updateChart: AgentTool = {\n\t\tname: 'update_chart_in_document',\n\t\ttitle: 'Change a chart in the document',\n\t\tdescription:\n\t\t\t'Changes one chart (by id or position from list_document_charts) as one undo step: data (categories, series — given series replace all) and / or presentation fields, which merge into the current spec (axes and legend field by field), plus its size. All-or-nothing: invalid values change nothing and come back as sentences. Returns the chart\\'s id, position and new spec.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tchartId: { type: 'string', description: 'Chart id from list_document_charts.' },\n\t\t\t\tpos: { type: 'integer', minimum: 0, description: 'Document position of the chart (from list_document_charts), instead of chartId.' },\n\t\t\t\tchanges: { type: 'object', description: 'Fields to change; omitted fields keep their value.', properties: SPEC_PROPERTIES, additionalProperties: false },\n\t\t\t\twidth: SIZE_WIDTH,\n\t\t\t\theight: { ...SIZE_HEIGHT, description: 'Height in pixels (default: unchanged).' }\n\t\t\t},\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: { title: 'Change a chart in the document', ...WRITE_IDEMPOTENT },\n\t\thandler: ({ chartId, pos, changes, width, height }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst chart = findChart({ chartId, pos });\n\t\t\tif (!changes && width === undefined && height === undefined) throw new Error('Nothing to change: pass changes, width or height, e.g. {\"changes\": {\"type\": \"line\"}}.');\n\t\t\tconst s = chart.spec;\n\t\t\tconst patch = (changes ?? {}) as Record<string, any>;\n\t\t\tconst spec = checkedSpec({\n\t\t\t\t...s,\n\t\t\t\t...patch,\n\t\t\t\txAxis: patch.xAxis ? { ...s.xAxis, ...patch.xAxis } : s.xAxis,\n\t\t\t\tyAxis: patch.yAxis ? { ...s.yAxis, ...patch.yAxis } : s.yAxis,\n\t\t\t\tyAxis2: patch.yAxis2 ? { ...s.yAxis2, ...patch.yAxis2 } : s.yAxis2,\n\t\t\t\tlegend: patch.legend ? { ...s.legend, ...patch.legend } : s.legend\n\t\t\t});\n\t\t\tconst attrs = { ...chart.node.attrs, spec, ...(width !== undefined ? { width } : {}), ...(height !== undefined ? { height } : {}) };\n\t\t\ted.view.dispatch(closeHistory(ed.state.tr.setNodeMarkup(chart.pos, undefined, attrs)));\n\t\t\treturn { id: chart.id, pos: chart.pos, spec, width: attrs.width, height: attrs.height };\n\t\t}\n\t};\n\n\tconst insertSparkline: AgentTool = {\n\t\tname: 'insert_sparkline',\n\t\ttitle: 'Insert a sparkline',\n\t\tdescription: 'Inserts a word-sized chart into the text at a document position (or the cursor), as one undo step. Returns its position and stored values.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tvalues: { type: 'array', items: VALUE, minItems: 1, maxItems: MAX_SPARKLINE_VALUES, description: 'Values in order (null for a gap).' },\n\t\t\t\tkind: { type: 'string', enum: [...SPARKLINE_KINDS], description: 'line (default), column, or win-loss (sign only).' },\n\t\t\t\tat: { type: 'integer', minimum: 0, description: 'Document position inside a paragraph (default: the selection).' },\n\t\t\t\twidth: { type: 'number', minimum: 4, maximum: 1000, description: 'Width in pixels (default 80).' },\n\t\t\t\theight: { type: 'number', minimum: 4, maximum: 200, description: 'Height in pixels (default 18).' },\n\t\t\t\tcolor: { type: 'string', description: 'CSS colour (default: the first palette colour).' },\n\t\t\t\tlabel: { type: 'string', description: 'Accessible label (default: \"Trend from <first> to <last>\").' }\n\t\t\t},\n\t\t\trequired: ['values'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: { title: 'Insert a sparkline', ...WRITE },\n\t\thandler: ({ values, kind = 'line', at, width = 80, height = 18, color = null, label = null }) => {\n\t\t\tconst ed = editor();\n\t\t\tcheckPosition(ed, at);\n\t\t\tconst parsed = (values as unknown[]).map((v) => parseChartValue(v));\n\t\t\tconst bad = parsed.findIndex((v) => v === undefined);\n\t\t\tif (bad >= 0) throw new Error(`values[${bad}] (\"${String(values[bad])}\") is not a number; pass numbers, numeric text or null. Nothing was inserted.`);\n\t\t\tconst pos = at ?? ed.state.selection.from;\n\t\t\tif (!ed.state.doc.resolve(pos).parent.inlineContent) throw new Error(`Position ${pos} is between blocks; a sparkline goes inside a paragraph (use a position from get_text {blocks: true}).`);\n\t\t\tconst stored = normalizeSparklineValues(parsed);\n\t\t\ted.chain().command(({ tr }) => (closeHistory(tr), true)).insertContentAt(pos, { type: 'sparkline', attrs: { values: stored, kind, width, height, color, label } }).run();\n\t\t\treturn { pos, kind, values: stored };\n\t\t}\n\t};\n\n\treturn {\n\t\tread: () => (chartType() ? [listCharts] : []),\n\t\twrite: () => (chartType() ? [insertChart, updateChart, ...(editor().schema.nodes.sparkline ? [insertSparkline] : [])] : [])\n\t};\n}\n","import { hasVariantCommands, type ComponentCommandTarget } from '../commands/builtins.js';\nimport type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport type { ToolProviderOptions } from './providers.js';\n\nconst IDS = { type: 'array', items: { type: 'string' }, description: 'Node ids (default: the selection).' } as const;\nconst ID = { type: 'string', description: 'An instance, or a layer inside one (default: the first selected node).' } as const;\n\nconst COMPONENT_ID = { type: 'string', description: 'Component or component set id (from list_components).' } as const;\nconst PARENT_ID = { type: 'string', description: 'Frame to place the instance in (default: the canvas root).' } as const;\nconst X = { type: 'number', description: 'X position (canvas coordinates, or relative to the parent frame).' } as const;\nconst Y = { type: 'number', description: 'Y position (canvas coordinates, or relative to the parent frame).' } as const;\n\nconst READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\nconst WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false } as const;\nconst DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false } as const;\n\n/** Tool titles shown by MCP clients. */\nconst TITLES: Record<string, string> = {\n\tlist_components: 'List components',\n\tinspect_instance: 'Inspect an instance',\n\tcreate_component: 'Create a component',\n\tcombine_as_variants: 'Combine as variants',\n\tcreate_instance: 'Create an instance',\n\tset_instance_properties: 'Set instance properties',\n\tswap_instance: 'Swap an instance',\n\treset_overrides: 'Reset overrides',\n\tpush_overrides: 'Push overrides to the main component',\n\tdetach_instance: 'Detach instances',\n\tgo_to_main_component: 'Go to the main component',\n\trestore_main_component: 'Restore the main component',\n\tadd_variant: 'Add a variant',\n\tremove_variant: 'Remove a variant',\n\tset_variant_properties: 'Set variant properties',\n\trename_variant_property: 'Rename a variant property',\n\trename_variant_value: 'Rename a variant value'\n};\n\n/** Tools that discard state the agent cannot get back by calling another tool. */\nconst DESTRUCTIVE_TOOLS = new Set(['reset_overrides', 'push_overrides', 'detach_instance', 'remove_variant']);\n\ntype Outcome = { ok: true; ids: string[] } | { ok: false; reason: string };\nconst check = (result: Outcome) => {\n\tif (!result.ok) throw new Error(result.reason);\n\treturn { ids: result.ids };\n};\n\n/**\n * Agent tools for canvas components and instances, backed by a components controller\n * (`createCanvasComponentsController`): `list_components`, `inspect_instance`,\n * `create_component`, `combine_as_variants`, `create_instance`, `set_instance_properties`\n * (variants and component properties), `swap_instance`, `reset_overrides`, `push_overrides`,\n * `detach_instance`, `go_to_main_component`, `restore_main_component` and (when the controller\n * has them) the variant editors `add_variant`, `remove_variant`, `set_variant_properties`,\n * `rename_variant_property` and `rename_variant_value`. Refused actions come back as tool errors\n * carrying the reason.\n */\nexport function createCanvasComponentToolProvider(\n\tcontroller: ComponentCommandTarget,\n\toptions: ToolProviderOptions = { id: 'canvas-components' }\n): AgentToolProvider {\n\tconst tool = (\n\t\tname: string,\n\t\tdescription: string,\n\t\tproperties: Record<string, unknown>,\n\t\thandler: AgentTool['handler'],\n\t\textra: { required?: string[]; readOnly?: boolean } = {}\n\t): AgentTool => ({\n\t\tname,\n\t\ttitle: TITLES[name],\n\t\tdescription,\n\t\tinputSchema: { type: 'object', properties: properties as never, ...(extra.required ? { required: extra.required } : {}), additionalProperties: false },\n\t\tannotations: {\n\t\t\ttitle: TITLES[name],\n\t\t\t...(extra.readOnly ? READ : DESTRUCTIVE_TOOLS.has(name) ? DESTRUCTIVE : WRITE)\n\t\t},\n\t\thandler\n\t});\n\tconst tools: AgentTool[] = [\n\t\ttool('list_components', 'Lists components (id, name, variant values, instance count) and component sets with their variant options.', {}, () => controller.list(), {\n\t\t\treadOnly: true\n\t\t}),\n\t\ttool(\n\t\t\t'inspect_instance',\n\t\t\t'Describes an instance: its main component, variant, component property values (with types) and the overrides of its layers.',\n\t\t\t{ id: ID },\n\t\t\t({ id }) => {\n\t\t\t\tconst info = controller.inspect(id);\n\t\t\t\tif (!info) throw new Error('Not an instance (or a layer inside one).');\n\t\t\t\treturn info;\n\t\t\t},\n\t\t\t{ readOnly: true }\n\t\t),\n\t\ttool('create_component', 'Turns the given frame into a main component, or wraps the given layers in a new one.', { ids: IDS }, ({ ids }) =>\n\t\t\tcheck(controller.createComponent(ids))\n\t\t),\n\t\ttool('combine_as_variants', 'Wraps sibling components in a component set (variants named \"Property=Value, …\").', { ids: IDS }, ({ ids }) =>\n\t\t\tcheck(controller.combineAsVariants(ids))\n\t\t),\n\t\ttool(\n\t\t\t'create_instance',\n\t\t\t'Places an instance of a component or component set, optionally inside a frame, at a position, with property values.',\n\t\t\t{\n\t\t\t\tcomponentId: COMPONENT_ID,\n\t\t\t\tparentId: PARENT_ID,\n\t\t\t\tx: X,\n\t\t\t\ty: Y,\n\t\t\t\tproperties: { type: 'object', additionalProperties: true, description: 'Variant and component property values.' }\n\t\t\t},\n\t\t\t({ componentId, parentId, x, y, properties }) =>\n\t\t\t\tcheck(\n\t\t\t\t\tcontroller.createInstance(componentId, {\n\t\t\t\t\t\t...(parentId !== undefined ? { parentId } : {}),\n\t\t\t\t\t\t...(x !== undefined || y !== undefined ? { position: { x: x ?? 0, y: y ?? 0 } } : {}),\n\t\t\t\t\t\t...(properties ? { properties } : {})\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t{ required: ['componentId'] }\n\t\t),\n\t\ttool(\n\t\t\t'set_instance_properties',\n\t\t\t'Sets property values of instances: variant properties switch the variant (keeping compatible overrides), booleans toggle layers, text sets text, instance swap takes a component id.',\n\t\t\t{\n\t\t\t\tproperties: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\tdescription: 'Property name → value (see inspect_instance for the names and types).'\n\t\t\t\t},\n\t\t\t\tids: IDS\n\t\t\t},\n\t\t\t({ properties, ids }) => check(controller.setInstanceProperties(properties, ids)),\n\t\t\t{ required: ['properties'] }\n\t\t),\n\t\ttool(\n\t\t\t'swap_instance',\n\t\t\t'Makes instances show another component (or a set’s default variant), keeping overrides of layers with matching names.',\n\t\t\t{ componentId: COMPONENT_ID, ids: IDS },\n\t\t\t({ componentId, ids }) => check(controller.swapInstance(componentId, ids)),\n\t\t\t{ required: ['componentId'] }\n\t\t),\n\t\ttool(\n\t\t\t'reset_overrides',\n\t\t\t'Resets overrides: of a whole instance (default), of one layer, or one override group such as \"data.label\" or \"shape.fill\".',\n\t\t\t{ id: ID, group: { type: 'string', description: 'Override group, e.g. \"data.label\" or \"shape.fill\" (default: all).' } },\n\t\t\t({ id, group }) => check(controller.resetOverrides(id, group))\n\t\t),\n\t\ttool('push_overrides', 'Copies an instance’s overrides to its main component (and so to every other instance).', { id: ID }, ({ id }) =>\n\t\t\tcheck(controller.pushOverrides(id))\n\t\t),\n\t\ttool('detach_instance', 'Turns instances into plain frames that keep their current look.', { ids: IDS }, ({ ids }) => check(controller.detachInstance(ids))),\n\t\ttool('go_to_main_component', 'Selects the main component of an instance and returns its id.', { id: ID }, ({ id }) => {\n\t\t\tconst main = controller.goToMainComponent(id);\n\t\t\tif (!main) throw new Error('The main component was not found.');\n\t\t\treturn { id: main };\n\t\t}),\n\t\ttool('restore_main_component', 'Turns an instance whose main component was deleted back into the main component.', { id: ID }, ({ id }) =>\n\t\t\tcheck(controller.restoreMainComponent(id))\n\t\t)\n\t];\n\tif (hasVariantCommands(controller)) {\n\t\tconst SET = { type: 'string', description: 'Component set, or one of its variants (default: the selection’s set).' };\n\t\tconst VARIANT = { type: 'string', description: 'Variant id (default: the first selected variant).' };\n\t\tconst VALUES = { type: 'object', additionalProperties: { type: 'string' }, description: 'Variant property → value, e.g. { \"Size\": \"Medium\" }.' };\n\t\ttools.push(\n\t\t\ttool(\n\t\t\t\t'add_variant',\n\t\t\t\t'Adds a variant to a component set by duplicating one (default: the set’s default variant) with new property values. Unnamed properties keep the source’s values; new property names are added to every variant (\"Default\"). A combination another variant has is refused. Without values the next \"VariantN\" is used.',\n\t\t\t\t{ setId: SET, from: { type: 'string', description: 'Variant to duplicate.' }, variant: VALUES, x: X, y: Y },\n\t\t\t\t({ setId, from, variant, x, y }) =>\n\t\t\t\t\tcheck(\n\t\t\t\t\t\tcontroller.addVariant(setId, {\n\t\t\t\t\t\t\t...(from !== undefined ? { from } : {}),\n\t\t\t\t\t\t\t...(variant ? { variant } : {}),\n\t\t\t\t\t\t\t...(x !== undefined || y !== undefined ? { position: { x: x ?? 0, y: y ?? 0 } } : {})\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t),\n\t\t\ttool(\n\t\t\t\t'remove_variant',\n\t\t\t\t'Removes a variant from its component set (not the last one). Its instances switch to the closest remaining variant, keeping overrides.',\n\t\t\t\t{ id: VARIANT },\n\t\t\t\t({ id }) => check(controller.removeVariant(id))\n\t\t\t),\n\t\t\ttool(\n\t\t\t\t'set_variant_properties',\n\t\t\t\t'Sets a variant’s property values (merged with its current ones); new property names are added to every variant. A combination another variant has is refused.',\n\t\t\t\t{ values: VALUES, id: VARIANT },\n\t\t\t\t({ values, id }) => check(controller.setVariantProperties(values, id)),\n\t\t\t\t{ required: ['values'] }\n\t\t\t),\n\t\t\ttool(\n\t\t\t\t'rename_variant_property',\n\t\t\t\t'Renames a variant property across a component set (instances keep their variant).',\n\t\t\t\t{\n\t\t\t\t\tname: { type: 'string', description: 'Current property name.' },\n\t\t\t\t\tnewName: { type: 'string', description: 'New property name.' },\n\t\t\t\t\tsetId: SET\n\t\t\t\t},\n\t\t\t\t({ name, newName, setId }) => check(controller.renameVariantProperty(name, newName, setId)),\n\t\t\t\t{ required: ['name', 'newName'] }\n\t\t\t),\n\t\t\ttool(\n\t\t\t\t'rename_variant_value',\n\t\t\t\t'Renames a value of a variant property across a component set (instances keep their variant).',\n\t\t\t\t{\n\t\t\t\t\tproperty: { type: 'string', description: 'Variant property name.' },\n\t\t\t\t\tvalue: { type: 'string', description: 'Current value.' },\n\t\t\t\t\tnewValue: { type: 'string', description: 'New value.' },\n\t\t\t\t\tsetId: SET\n\t\t\t\t},\n\t\t\t\t({ property, value, newValue, setId }) => check(controller.renameVariantValue(property, value, newValue, setId)),\n\t\t\t\t{ required: ['property', 'value', 'newValue'] }\n\t\t\t)\n\t\t);\n\t}\n\treturn { id: options.id, namespace: options.namespace, tools };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/agent/index.cjs","../../src/agent/planner.ts","../../src/agent/providers.ts","../../src/agent/documentChartTools.ts","../../src/agent/componentTools.ts"],"names":["range","WRITE"],"mappings":"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CA,MAAA;AACc,QAAA;AACH,QAAA;AACP,QAAA;AACA,QAAA;AACoE,QAAA;AACxE,MAAA;AAEC,MAAA;AACwC,QAAA;AACO,UAAA;AACsC,UAAA;AAChD,UAAA;AACnC,QAAA;AACF,MAAA;AAC2B,MAAA;AAC7B,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACa,QAAA;AACL,UAAA;AACgB,UAAA;AACT,UAAA;AACd,QAAA;AACK,QAAA;AACN,MAAA;AACgF,MAAA;AACrD,MAAA;AAC5B,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACsC,MAAA;AACmC,MAAA;AAC7C,MAAA;AAC7B,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACgD,MAAA;AACa,MAAA;AAC9D,IAAA;AACA,IAAA;AAAK,MAAA;AAAkB,MAAA;AAAmG,MAAA;AACvF,MAAA;AACnC,IAAA;AAC6F,IAAA;AAChE,IAAA;AACgB,MAAA;AACkB,MAAA;AAC5C,MAAA;AAClB,IAAA;AACD,IAAA;AAAK,MAAA;AAA0B,MAAA;AAA6F,MAAA;AAClF,MAAA;AAC1C,IAAA;AACD,EAAA;AACoC,EAAA;AACQ,IAAA;AACI,IAAA;AACyC,IAAA;AAClF,IAAA;AACL,MAAA;AACC,QAAA;AACA,QAAA;AACuF,QAAA;AAEtF,QAAA;AAC8B,UAAA;AACS,YAAA;AACR,YAAA;AACsD,YAAA;AACnF,UAAA;AACF,QAAA;AACF,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACc,QAAA;AACgC,QAAA;AAC/C,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AAC8B,QAAA;AACuC,QAAA;AAC9C,QAAA;AACxB,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AAC+D,UAAA;AACD,UAAA;AACtD,UAAA;AACR,QAAA;AACyF,QAAA;AACzD,QAAA;AACjC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACmE,UAAA;AACX,UAAA;AACD,UAAA;AAC/C,UAAA;AACR,QAAA;AACwF,QAAA;AAC1C,QAAA;AAC/C,MAAA;AACD,IAAA;AACD,EAAA;AAC6D,EAAA;AAC9D;AJ65CiG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/agent/index.cjs","sourcesContent":[null,"import { ensureLayout, getLayoutedNodes, placeBeside } from '../canvas/layout.js';\nimport type { CanvasEdge, CanvasNode, Rect, Side } from '../canvas/types.js';\nimport type { AgentCommand, AgentStep, EditorSnapshot } from './types.js';\n\n/**\n * Normalizes a placement side from either the planner vocabulary (`right | below | left | above`)\n * or the MCP tool schema vocabulary (`top | right | bottom | left`). Unknown values fall back to `right`.\n */\nexport function normalizeSide(side: string | undefined | null): Side {\n\tswitch (side) {\n\t\tcase 'top':\n\t\tcase 'above':\n\t\t\treturn 'above';\n\t\tcase 'bottom':\n\t\tcase 'below':\n\t\t\treturn 'below';\n\t\tcase 'left':\n\t\t\treturn 'left';\n\t\tcase 'right':\n\t\tdefault:\n\t\t\treturn 'right';\n\t}\n}\n\n/**\n * Plans a sequence of atomic execution steps from a high-level agent command.\n */\nexport function planAgentCommand<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tcommand: AgentCommand<TNode, TEdge>,\n\tsnapshot: EditorSnapshot<TNode, TEdge>,\n\toptions: {\n\t\tdefaultNodeSize?: { width: number; height: number };\n\t\tidGenerator?: () => string;\n\t} = {}\n): AgentStep<TNode, TEdge>[] {\n\tconst defaultNodeSize = options.defaultNodeSize ?? { width: 200, height: 60 };\n\tconst idGen = options.idGenerator ?? (() => `node-${Math.random().toString(36).substring(2, 9)}`);\n\tconst edgeIdGen = () => `edge-${Math.random().toString(36).substring(2, 9)}`;\n\n\tconst nodes = snapshot.canvas?.nodes ?? [];\n\tconst edges = snapshot.canvas?.edges ?? [];\n\n\tswitch (command.type) {\n\t\tcase 'canvas:add_node': {\n\t\t\tlet pos = command.node.position;\n\t\t\t// Only anchor to (and connect from) a node that actually exists.\n\t\t\tconst anchorId =\n\t\t\t\tcommand.nearNodeId && nodes.some((n) => n.id === command.nearNodeId)\n\t\t\t\t\t? command.nearNodeId\n\t\t\t\t\t: undefined;\n\n\t\t\tif (!pos) {\n\t\t\t\tconst rects: Record<string, Rect> = {};\n\t\t\t\tfor (const n of nodes) {\n\t\t\t\t\trects[n.id] = {\n\t\t\t\t\t\tx: n.position.x,\n\t\t\t\t\t\ty: n.position.y,\n\t\t\t\t\t\twidth: n.measured?.width ?? (n as any).width ?? defaultNodeSize.width,\n\t\t\t\t\t\theight: n.measured?.height ?? (n as any).height ?? defaultNodeSize.height\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (anchorId) {\n\t\t\t\t\t// Use collision-aware directional placement\n\t\t\t\t\tpos = placeBeside(rects, defaultNodeSize, normalizeSide(command.side), anchorId);\n\t\t\t\t} else if (nodes.length > 0) {\n\t\t\t\t\t// Default beside the last node\n\t\t\t\t\tconst lastNode = nodes[nodes.length - 1];\n\t\t\t\t\tpos = placeBeside(rects, defaultNodeSize, 'right', lastNode.id);\n\t\t\t\t} else {\n\t\t\t\t\tpos = { x: 100, y: 100 };\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst fullNode: TNode = {\n\t\t\t\t...command.node,\n\t\t\t\tid: command.node.id || idGen(),\n\t\t\t\tposition: pos\n\t\t\t} as TNode;\n\n\t\t\tconst steps: AgentStep<TNode, TEdge>[] = [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_add_node',\n\t\t\t\t\tnode: fullNode\n\t\t\t\t}\n\t\t\t];\n\n\t\t\t// If attached to nearNode, optionally automatically connect\n\t\t\tif (anchorId) {\n\t\t\t\tsteps.push({\n\t\t\t\t\ttype: 'step:canvas_add_edge',\n\t\t\t\t\tedge: {\n\t\t\t\t\t\tid: edgeIdGen(),\n\t\t\t\t\t\tsource: anchorId,\n\t\t\t\t\t\ttarget: fullNode.id\n\t\t\t\t\t} as TEdge\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn steps;\n\t\t}\n\n\t\tcase 'canvas:remove_nodes': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_remove_nodes',\n\t\t\t\t\tnodeIds: command.nodeIds\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:connect': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_add_edge',\n\t\t\t\t\tedge: {\n\t\t\t\t\t\tid: edgeIdGen(),\n\t\t\t\t\t\tsource: command.source,\n\t\t\t\t\t\ttarget: command.target,\n\t\t\t\t\t\tlabel: command.label\n\t\t\t\t\t} as TEdge\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:update_node': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_update_node',\n\t\t\t\t\tid: command.id,\n\t\t\t\t\tdata: command.data\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:layout': {\n\t\t\tconst dir = command.direction ?? 'LR';\n\t\t\tlet layouted: TNode[];\n\n\t\t\t// Matches the MCP tool schema: `incremental` defaults to true.\n\t\t\tif (command.incremental ?? true) {\n\t\t\t\tconst stored: Record<string, { x: number; y: number }> = {};\n\t\t\t\tfor (const n of nodes) {\n\t\t\t\t\tif (n.position && (n.position.x !== 0 || n.position.y !== 0)) {\n\t\t\t\t\t\tstored[n.id] = { ...n.position };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlayouted = ensureLayout(nodes, edges, {\n\t\t\t\t\tdirection: dir,\n\t\t\t\t\tstoredPositions: stored\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tlayouted = getLayoutedNodes(nodes, edges, undefined, { direction: dir });\n\t\t\t}\n\n\t\t\tconst positions: Record<string, { x: number; y: number }> = {};\n\t\t\tfor (const node of layouted) {\n\t\t\t\tpositions[node.id] = { ...node.position };\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_move_nodes',\n\t\t\t\t\tpositions\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:select': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_select',\n\t\t\t\t\tnodeIds: command.nodeIds\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'canvas:highlight': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:canvas_highlight',\n\t\t\t\t\tnodeIds: command.nodeIds,\n\t\t\t\t\tcolor: command.color ?? '#3b82f6',\n\t\t\t\t\tdurationMs: command.durationMs ?? 2000\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'prompt:insert_text': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:prompt_insert',\n\t\t\t\t\ttext: command.text,\n\t\t\t\t\tat: command.at\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'prompt:replace_range': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:prompt_replace',\n\t\t\t\t\tfrom: command.from,\n\t\t\t\t\tto: command.to,\n\t\t\t\t\ttext: command.text\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'prompt:set_variable': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:prompt_set_variable',\n\t\t\t\t\tname: command.name,\n\t\t\t\t\tvalue: command.value\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'prompt:select': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:prompt_select',\n\t\t\t\t\tfrom: command.from,\n\t\t\t\t\tto: command.to\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'choreography:say': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:say',\n\t\t\t\t\ttext: command.text,\n\t\t\t\t\tatNodeId: command.atNodeId,\n\t\t\t\t\tposition: command.position\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'choreography:wait': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:wait',\n\t\t\t\t\tms: command.ms\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tcase 'tool:call': {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'step:tool_call',\n\t\t\t\t\tname: command.name,\n\t\t\t\t\targuments: command.arguments ?? {}\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\n\t\tdefault: {\n\t\t\treturn [];\n\t\t}\n\t}\n}\n\nconst CANVAS_TOOL_OPERATIONS: Record<string, string> = {\n\tcanvas_add_node: 'add_node',\n\tcanvas_remove_nodes: 'remove_nodes',\n\tcanvas_connect: 'connect',\n\tcanvas_update_node: 'update_node',\n\tcanvas_layout: 'layout',\n\tcanvas_select: 'select',\n\tcanvas_highlight: 'highlight'\n};\n\nconst PROMPT_TOOL_OPERATIONS: Record<string, string> = {\n\tprompt_insert_text: 'insert_text',\n\tprompt_replace_range: 'replace_range',\n\tprompt_set_variable: 'set_variable',\n\tprompt_select: 'select'\n};\n\n/**\n * Converts an MCP tool call into a planner command. `edit_canvas` /\n * `edit_prompt` and the per-operation `canvas_*` / `prompt_*` tools (see\n * `tools/definitions.ts`) map to canvas/prompt commands using the MCP\n * schema's vocabulary; every other tool, including dynamic `ToolRegistry`\n * tools, becomes a `tool:call` command. Returns `null` when a static tool's\n * arguments are missing what the operation needs.\n */\nexport function toolCallToAgentCommand<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(name: string, args: Record<string, any> = {}): AgentCommand<TNode, TEdge> | null {\n\tconst ids = (value: unknown): string[] | null =>\n\t\tArray.isArray(value) && value.every((v) => typeof v === 'string') ? value : null;\n\tconst num = (value: unknown) => typeof value === 'number' && Number.isFinite(value);\n\n\tconst canvasOperation = name === 'edit_canvas' ? args.operation : CANVAS_TOOL_OPERATIONS[name];\n\tif (canvasOperation !== undefined) {\n\t\tswitch (canvasOperation) {\n\t\t\tcase 'add_node':\n\t\t\t\treturn args.node && typeof args.node === 'object' && !Array.isArray(args.node)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttype: 'canvas:add_node',\n\t\t\t\t\t\t\tnode: { ...args.node, data: args.node.data ?? {} },\n\t\t\t\t\t\t\tnearNodeId: args.nearNodeId,\n\t\t\t\t\t\t\tside: args.side\n\t\t\t\t\t\t}\n\t\t\t\t\t: null;\n\t\t\tcase 'remove_nodes':\n\t\t\t\treturn ids(args.nodeIds) ? { type: 'canvas:remove_nodes', nodeIds: args.nodeIds } : null;\n\t\t\tcase 'connect':\n\t\t\t\treturn typeof args.source === 'string' && typeof args.target === 'string'\n\t\t\t\t\t? { type: 'canvas:connect', source: args.source, target: args.target, label: args.label }\n\t\t\t\t\t: null;\n\t\t\tcase 'update_node': {\n\t\t\t\tconst id = args.node?.id ?? args.id;\n\t\t\t\treturn typeof id === 'string'\n\t\t\t\t\t? { type: 'canvas:update_node', id, data: args.node?.data ?? args.data ?? {} }\n\t\t\t\t\t: null;\n\t\t\t}\n\t\t\tcase 'layout':\n\t\t\t\treturn { type: 'canvas:layout', direction: args.direction, incremental: args.incremental };\n\t\t\tcase 'select':\n\t\t\t\treturn ids(args.nodeIds) ? { type: 'canvas:select', nodeIds: args.nodeIds } : null;\n\t\t\tcase 'highlight':\n\t\t\t\treturn ids(args.nodeIds)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttype: 'canvas:highlight',\n\t\t\t\t\t\t\tnodeIds: args.nodeIds,\n\t\t\t\t\t\t\t...(typeof args.color === 'string' ? { color: args.color } : {}),\n\t\t\t\t\t\t\t...(num(args.durationMs) ? { durationMs: args.durationMs } : {})\n\t\t\t\t\t\t}\n\t\t\t\t\t: null;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\tconst promptOperation = name === 'edit_prompt' ? args.operation : PROMPT_TOOL_OPERATIONS[name];\n\tif (promptOperation !== undefined) {\n\t\tswitch (promptOperation) {\n\t\t\tcase 'insert_text':\n\t\t\t\treturn typeof args.text === 'string'\n\t\t\t\t\t? { type: 'prompt:insert_text', text: args.text, at: num(args.at) ? args.at : undefined }\n\t\t\t\t\t: null;\n\t\t\tcase 'replace_range':\n\t\t\t\treturn typeof args.text === 'string' && num(args.from) && num(args.to)\n\t\t\t\t\t? { type: 'prompt:replace_range', from: args.from, to: args.to, text: args.text }\n\t\t\t\t\t: null;\n\t\t\tcase 'set_variable':\n\t\t\t\treturn typeof args.name === 'string'\n\t\t\t\t\t? { type: 'prompt:set_variable', name: args.name, value: args.value }\n\t\t\t\t\t: null;\n\t\t\tcase 'select':\n\t\t\t\treturn num(args.from) && num(args.to) ? { type: 'prompt:select', from: args.from, to: args.to } : null;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\treturn { type: 'tool:call', name, arguments: args };\n}\n\n/**\n * Checks a command against the snapshot before it is planned and returns a\n * message an agent can act on, or `null` when it is valid: node ids must\n * exist (and a new node's id must not), an edge needs two different existing\n * nodes, and prompt ranges must be ordered. Prompt positions are host-defined,\n * so their upper bound is left to the host's `applySteps`.\n * The planner itself stays lenient (it falls back instead of failing), so\n * tool providers call this first.\n */\nexport function validateAgentCommand<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(command: AgentCommand<TNode, TEdge>, snapshot: EditorSnapshot<TNode, TEdge>): string | null {\n\tconst known = new Set((snapshot.canvas?.nodes ?? []).map((n) => n.id));\n\tconst missing = (nodeIds: readonly string[]) => {\n\t\tconst unknown = nodeIds.filter((id) => !known.has(id));\n\t\treturn unknown.length\n\t\t\t? `Unknown node id${unknown.length > 1 ? 's' : ''}: ${unknown.map((id) => JSON.stringify(id)).join(', ')}. Call get_editor_state for the current ids.`\n\t\t\t: null;\n\t};\n\tconst range = (from: number, to: number) => {\n\t\tif (from < 0 || to < 0) return 'Positions must be >= 0.';\n\t\tif (from > to) return `\"from\" (${from}) must not be after \"to\" (${to}).`;\n\t\treturn null;\n\t};\n\n\tswitch (command.type) {\n\t\tcase 'canvas:add_node':\n\t\t\tif (command.node.id && known.has(command.node.id)) return `A node with id \"${command.node.id}\" already exists.`;\n\t\t\treturn command.nearNodeId ? missing([command.nearNodeId]) : null;\n\t\tcase 'canvas:remove_nodes':\n\t\tcase 'canvas:highlight':\n\t\t\treturn command.nodeIds.length ? missing(command.nodeIds) : 'Pass at least one node id.';\n\t\tcase 'canvas:select':\n\t\t\treturn missing(command.nodeIds);\n\t\tcase 'canvas:connect':\n\t\t\tif (command.source === command.target) return 'An edge needs two different nodes.';\n\t\t\treturn missing([command.source, command.target]);\n\t\tcase 'canvas:update_node':\n\t\t\treturn missing([command.id]);\n\t\tcase 'prompt:insert_text':\n\t\t\treturn command.at !== undefined ? range(command.at, command.at) : null;\n\t\tcase 'prompt:replace_range':\n\t\tcase 'prompt:select':\n\t\t\treturn range(command.from, command.to);\n\t\tcase 'choreography:say':\n\t\t\treturn command.atNodeId ? missing([command.atNodeId]) : null;\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Plans the steps for an MCP tool call: static editor tools are planned like\n * the equivalent commands, any other tool becomes a `step:tool_call` to be\n * executed with `ToolRegistry.callTool`. Returns `[]` for invalid arguments.\n */\nexport function planToolCall<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tname: string,\n\targs: Record<string, any>,\n\tsnapshot: EditorSnapshot<TNode, TEdge>,\n\toptions?: Parameters<typeof planAgentCommand>[2]\n): AgentStep<TNode, TEdge>[] {\n\tconst command = toolCallToAgentCommand<TNode, TEdge>(name, args);\n\treturn command ? planAgentCommand(command, snapshot, options) : [];\n}\n","import type { Editor } from '@tiptap/core';\nimport { getEditorText } from '../serializers/plain-text.js';\nimport { getEditorMarkdown } from '../serializers/markdown.js';\nimport { TextSelection } from '@tiptap/pm/state';\nimport {\n\tTABLE_ACTIONS,\n\tgetTableActionState,\n\tgetTableState,\n\tisTableEnabled,\n\trunTableAction,\n\ttype TableAction\n} from '../extensions/table/actions.js';\nimport { applyReplacements, searchDocument, type FindMatch } from '../extensions/find-replace/search.js';\nimport type { FindOptions } from '../extensions/find-replace/pattern.js';\nimport type { SpreadsheetController } from '../spreadsheet/controller.js';\nimport { colNameToIndex, indexToColName, isFormulaError } from '../spreadsheet/formula/evaluator.js';\nimport { isSingleCellRange, normalizeRange, rangeContainsRange, rangesIntersect } from '../spreadsheet/table.js';\nimport type { CellRange } from '../spreadsheet/types.js';\nimport { planAgentCommand, toolCallToAgentCommand, validateAgentCommand, type planToolCall } from './planner.js';\nimport type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport {\n\tEDIT_OPERATION_REQUIRED_ARGUMENTS,\n\tEDITOR_CANVAS_TOOLS,\n\tEDITOR_PROMPT_TOOLS,\n\tMCP_TOOL_EDIT_CANVAS,\n\tMCP_TOOL_EDIT_PROMPT,\n\tMCP_TOOL_GET_EDITOR_STATE,\n\tMCP_TOOL_VIEW_EDITOR\n} from '../tools/definitions.js';\nimport type { AgentStep, EditorSnapshot } from './types.js';\nimport { createDocumentChartTools } from './documentChartTools.js';\nimport type { CanvasEdge, CanvasNode } from '../canvas/types.js';\nimport {\n\tDEFAULT_AGENT_CARET_COLOR,\n\tDEFAULT_AGENT_CARET_NAME,\n\tclearAgentCaret,\n\tmarkAgentChanges,\n\treplaceWithTextTransaction,\n\tsetAgentCaret,\n\ttypeAgentText,\n\ttype AgentTypingOptions\n} from '../extensions/agent-caret.js';\n\nexport interface ToolProviderOptions {\n\t/** Provider id (unique per registry). */\n\tid: string;\n\t/** Always prefix tool names with `<namespace>__`. */\n\tnamespace?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Rich-text editor provider\n// ---------------------------------------------------------------------------\n\n/** A TipTap editor, or anything exposing one as `.editor` (e.g. `EditorController`). */\nexport type RichTextToolTarget = Editor | { editor: Editor };\n\nconst toEditor = (target: RichTextToolTarget): Editor =>\n\t'commands' in target ? (target as Editor) : (target as { editor: Editor }).editor;\n\nfunction selectionInfo(editor: Editor) {\n\tconst { from, to } = editor.state.selection;\n\treturn { from, to, text: editor.state.doc.textBetween(from, to, '\\n') };\n}\n\n/** Throws a tool error unless `from..to` is an ordered range inside the document. */\nfunction checkRange(editor: Editor, from: number, to: number) {\n\tconst size = editor.state.doc.content.size;\n\tif (from > to) throw new Error(`\"from\" (${from}) must not be after \"to\" (${to}).`);\n\tif (to > size) throw new Error(`Position ${to} is outside the document (size ${size}).`);\n}\n\n/** Textblocks (paragraphs, headings, cells...) with the positions their text spans. */\nfunction textBlocks(editor: Editor) {\n\tconst blocks: Array<{ type: string; from: number; to: number; text: string }> = [];\n\teditor.state.doc.descendants((node, pos) => {\n\t\tif (!node.isTextblock) return true;\n\t\tblocks.push({ type: node.type.name, from: pos + 1, to: pos + 1 + node.content.size, text: node.textContent });\n\t\treturn false;\n\t});\n\treturn blocks;\n}\n\n/** How the rich-text tools show the agent at work in the document. */\nexport interface RichTextAgentPresence {\n\t/** Caret id (default: the provider id). */\n\tid?: string;\n\t/** Name on the caret's tag (default \"Agent\"). */\n\tname?: string;\n\t/** CSS colour of the caret and its highlights (default violet). */\n\tcolor?: string;\n\t/**\n\t * Type inserted and replacement text out a few characters at a time (still one undo step).\n\t * Default false: edits land at once. `true` uses the default speed.\n\t */\n\ttyping?: boolean | AgentTypingOptions;\n\t/** Hide the caret this long after the last tool call, in ms (default 12000; 0 keeps it). */\n\tidleMs?: number;\n\t/** Keep changed text tinted this long, in ms (default 1500; 0 keeps the tint until the next edit). */\n\tchangedMs?: number;\n}\n\nexport interface RichTextToolProviderOptions extends ToolProviderOptions {\n\t/**\n\t * Draw the agent's caret in the document where it reads and writes, as collaborators' carets\n\t * look in Google Docs (default true). `false` leaves the document untouched by presence.\n\t */\n\tpresence?: boolean | RichTextAgentPresence;\n}\n\n/** Shows the agent's caret, selection and changes in a rich-text editor for the tools. */\nfunction createTextPresence(editor: () => Editor, providerId: string, option: RichTextToolProviderOptions['presence']) {\n\tconst config: RichTextAgentPresence | null = option === false ? null : option === true || option === undefined ? {} : option;\n\tconst caret = {\n\t\tid: config?.id ?? providerId,\n\t\tname: config?.name ?? DEFAULT_AGENT_CARET_NAME,\n\t\tcolor: config?.color ?? DEFAULT_AGENT_CARET_COLOR\n\t};\n\tconst typing: AgentTypingOptions | null = !config?.typing ? null : config.typing === true ? {} : config.typing;\n\tconst idleMs = config?.idleMs ?? 12000;\n\tconst changedMs = config?.changedMs ?? 1500;\n\tlet idleTimer: ReturnType<typeof setTimeout> | undefined;\n\tlet tintTimer: ReturnType<typeof setTimeout> | undefined;\n\n\tconst live = () => {\n\t\tconst ed = editor();\n\t\treturn config && ed && !ed.isDestroyed ? ed : null;\n\t};\n\tconst touch = () => {\n\t\tclearTimeout(idleTimer);\n\t\tif (idleMs > 0) idleTimer = setTimeout(() => live() && clearAgentCaret(live()!, caret.id), idleMs);\n\t};\n\tconst tint = (ed: Editor, ranges: Array<{ from: number; to: number }>) => {\n\t\tclearTimeout(tintTimer);\n\t\tmarkAgentChanges(ed, caret.id, ranges);\n\t\tif (changedMs > 0 && ranges.length) tintTimer = setTimeout(() => live() && markAgentChanges(live()!, caret.id, []), changedMs);\n\t};\n\n\treturn {\n\t\t/** Puts the caret at `head`, selecting from `anchor`. */\n\t\tshow(anchor: number, head = anchor) {\n\t\t\tconst ed = live();\n\t\t\tif (!ed) return;\n\t\t\tsetAgentCaret(ed, { ...caret, from: anchor, to: head });\n\t\t\ttouch();\n\t\t},\n\t\t/** Shows the caret after ranges just changed by a tool, tinting them. */\n\t\tchanged(ranges: Array<{ from: number; to: number }>) {\n\t\t\tconst ed = live();\n\t\t\tif (!ed || !ranges.length) return;\n\t\t\tconst last = ranges[ranges.length - 1];\n\t\t\tsetAgentCaret(ed, { ...caret, from: last.to });\n\t\t\ttint(ed, ranges);\n\t\t\ttouch();\n\t\t},\n\t\t/** Replaces `from..to` with literal text, typed out when typing is on. Returns the inserted range. */\n\t\tasync write(ed: Editor, from: number, to: number, text: string, signal?: AbortSignal) {\n\t\t\tif (live() && typing) {\n\t\t\t\tclearTimeout(tintTimer);\n\t\t\t\tconst range = await typeAgentText(ed, { ...typing, from, to, text, caret, signal });\n\t\t\t\tif (changedMs > 0) tintTimer = setTimeout(() => live() && markAgentChanges(live()!, caret.id, []), changedMs);\n\t\t\t\ttouch();\n\t\t\t\treturn range;\n\t\t\t}\n\t\t\tconst tr = replaceWithTextTransaction(ed.state, from, to, text);\n\t\t\tconst end = tr.mapping.map(to, 1);\n\t\t\tconst range = { from: Math.min(tr.mapping.map(from, -1), end), to: end };\n\t\t\ted.view.dispatch(tr.scrollIntoView());\n\t\t\tif (range.from < range.to) this.changed([range]);\n\t\t\telse this.show(range.to);\n\t\t\treturn range;\n\t\t},\n\t\tdispose() {\n\t\t\tclearTimeout(idleTimer);\n\t\t\tclearTimeout(tintTimer);\n\t\t}\n\t};\n}\n\nconst FIND_SCHEMA_PROPERTIES = {\n\tmatchCase: { type: 'boolean', description: 'Case-sensitive match (default false).' },\n\twholeWord: { type: 'boolean', description: 'Match whole words only (default false).' },\n\tregex: {\n\t\ttype: 'boolean',\n\t\tdescription: 'Treat the query as a JavaScript regular expression (default false: literal text).'\n\t}\n} as const;\n\nconst CONTEXT_CHARS = 30;\n\n/** Runs a literal (or regex) search; throws a tool error for an invalid pattern. */\nfunction findMatches(\n\teditor: Editor,\n\tquery: string,\n\targs: { matchCase?: boolean; wholeWord?: boolean; regex?: boolean },\n\treplacement?: string\n): { matches: FindMatch[]; truncated: boolean } {\n\tconst options: FindOptions = {\n\t\tmatchCase: !!args.matchCase,\n\t\twholeWord: !!args.wholeWord,\n\t\t...(args.regex ? { regex: true } : { literal: true })\n\t};\n\tconst result = searchDocument(editor.state.doc, query, options, replacement !== undefined ? { replacement } : {});\n\tif (result.error) throw new Error(`Invalid search pattern: ${result.error.message}`);\n\treturn { matches: result.matches, truncated: result.truncated };\n}\n\n/**\n * Tools for a mounted rich-text editor: `get_text` and `find_text` (always),\n * plus `insert_text`, `replace_text`, `replace_range`, `replace_selection` and\n * `set_selection` while the editor is editable, `edit_table` when the\n * schema has tables (`createRichTextPreset`'s `table`), and the chart tools\n * (`list_document_charts`; `insert_chart`, `update_chart_in_document` and\n * `insert_sparkline` while editable) when it has charts (`chart`).\n * Positions are ProseMirror document positions (as in `selection.from`), not\n * plain-text offsets; `find_text` and `get_text {blocks: true}` return them.\n * The provider re-announces its tool list when editability changes and\n * exposes no tools once the editor is destroyed.\n */\nexport function createRichTextToolProvider(\n\ttarget: RichTextToolTarget,\n\toptions: RichTextToolProviderOptions\n): AgentToolProvider {\n\tconst editor = () => toEditor(target);\n\tconst charts = createDocumentChartTools(editor);\n\tconst presence = createTextPresence(editor, options.id, options.presence);\n\n\tconst getText: AgentTool = {\n\t\tname: 'get_text',\n\t\ttitle: 'Get document text',\n\t\tdescription:\n\t\t\t'Returns the document content (plain text by default, or HTML / markdown), the current selection and the document size. ' +\n\t\t\t'Editing tools take document positions, not text offsets: pass `blocks: true` to get every paragraph with the positions ' +\n\t\t\t'its text spans, or use find_text to get the positions of a phrase.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tformat: {\n\t\t\t\t\ttype: 'string',\n\t\t\t\t\tenum: ['text', 'html', 'markdown'],\n\t\t\t\t\tdescription: 'Content format (default: text).'\n\t\t\t\t},\n\t\t\t\tblocks: {\n\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\tdescription: 'Also return `blocks`: each paragraph/heading/cell as {type, from, to, text} (default false).'\n\t\t\t\t}\n\t\t\t},\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Get document text',\n\t\t\treadOnlyHint: true,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ format = 'text', blocks = false }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst content =\n\t\t\t\tformat === 'html' ? ed.getHTML() : format === 'markdown' ? getEditorMarkdown(ed) : getEditorText(ed);\n\t\t\treturn {\n\t\t\t\tformat,\n\t\t\t\tcontent,\n\t\t\t\tselection: selectionInfo(ed),\n\t\t\t\tsize: ed.state.doc.content.size,\n\t\t\t\t...(blocks ? { blocks: textBlocks(ed) } : {})\n\t\t\t};\n\t\t}\n\t};\n\n\tconst insertText: AgentTool = {\n\t\tname: 'insert_text',\n\t\ttitle: 'Insert text',\n\t\tdescription:\n\t\t\t'Inserts literal text (newlines start new paragraphs) at a document position, or at the cursor when `at` is omitted. ' +\n\t\t\t'Returns the inserted `range` {from, to}.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\ttext: { type: 'string', description: 'Text to insert.' },\n\t\t\t\tat: { type: 'integer', minimum: 0, description: 'Document position (default: the selection).' }\n\t\t\t},\n\t\t\trequired: ['text'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Insert text',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: async ({ text, at }, { signal }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst size = ed.state.doc.content.size;\n\t\t\tif (at !== undefined && at > size) {\n\t\t\t\tthrow new Error(`Position ${at} is outside the document (size ${size}).`);\n\t\t\t}\n\t\t\tconst { from, to } = ed.state.selection;\n\t\t\tconst range = await presence.write(ed, at ?? from, at ?? to, text, signal);\n\t\t\treturn { inserted: text.length, range, selection: selectionInfo(ed) };\n\t\t}\n\t};\n\n\tconst findText: AgentTool = {\n\t\tname: 'find_text',\n\t\ttitle: 'Find text',\n\t\tdescription:\n\t\t\t'Finds a phrase (literal by default) and returns each match with its document positions {from, to}, ' +\n\t\t\t'ready for replace_range, insert_text `at` or set_selection, plus surrounding context.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tquery: { type: 'string', minLength: 1, description: 'Text (or regex) to find.' },\n\t\t\t\t...FIND_SCHEMA_PROPERTIES,\n\t\t\t\tmaxResults: { type: 'integer', minimum: 1, maximum: 500, description: 'Matches returned (default 50).' }\n\t\t\t},\n\t\t\trequired: ['query'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Find text',\n\t\t\treadOnlyHint: true,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ query, maxResults = 50, ...flags }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst { doc } = ed.state;\n\t\t\tconst { matches, truncated } = findMatches(ed, query, flags);\n\t\t\tif (matches.length) presence.show(matches[0].from, matches[0].to);\n\t\t\treturn {\n\t\t\t\ttotal: matches.length,\n\t\t\t\ttruncated: truncated || matches.length > maxResults,\n\t\t\t\tmatches: matches.slice(0, maxResults).map((m) => ({\n\t\t\t\t\tfrom: m.from,\n\t\t\t\t\tto: m.to,\n\t\t\t\t\ttext: m.text,\n\t\t\t\t\tbefore: doc.textBetween(Math.max(0, m.from - CONTEXT_CHARS), m.from, '\\n', ' '),\n\t\t\t\t\tafter: doc.textBetween(m.to, Math.min(doc.content.size, m.to + CONTEXT_CHARS), '\\n', ' ')\n\t\t\t\t}))\n\t\t\t};\n\t\t}\n\t};\n\n\tconst replaceText: AgentTool = {\n\t\tname: 'replace_text',\n\t\ttitle: 'Find and replace',\n\t\tdescription:\n\t\t\t'Replaces occurrences of a phrase with literal text in one undoable step, keeping the found text\\'s formatting. ' +\n\t\t\t'Replaces every occurrence by default, or only the n-th with `occurrence`. Fails when nothing matches.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tfind: { type: 'string', minLength: 1, description: 'Text (or regex) to find.' },\n\t\t\t\treplace: { type: 'string', description: 'Replacement text (literal; an empty string deletes the matches).' },\n\t\t\t\t...FIND_SCHEMA_PROPERTIES,\n\t\t\t\toccurrence: {\n\t\t\t\t\ttype: 'integer',\n\t\t\t\t\tminimum: 1,\n\t\t\t\t\tdescription: 'Only replace the n-th match (1-based, in document order). Default: all matches.'\n\t\t\t\t}\n\t\t\t},\n\t\t\trequired: ['find', 'replace'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Find and replace',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ find, replace, occurrence, ...flags }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst { matches } = findMatches(ed, find, flags);\n\t\t\tif (matches.length === 0) throw new Error(`No match for ${JSON.stringify(find)}.`);\n\t\t\tif (occurrence !== undefined && occurrence > matches.length) {\n\t\t\t\tthrow new Error(`Occurrence ${occurrence} requested, but there are only ${matches.length} matches.`);\n\t\t\t}\n\t\t\t// The replacement is literal text: `\\r` / `\\n` split paragraphs / insert line breaks.\n\t\t\tconst chosen = (occurrence !== undefined ? [matches[occurrence - 1]] : matches).map((m) => ({\n\t\t\t\t...m,\n\t\t\t\treplacement: String(replace).replace(/\\r\\n?|\\n/g, '\\r')\n\t\t\t}));\n\t\t\tconst tr = ed.state.tr;\n\t\t\tconst ends = applyReplacements(tr, chosen);\n\t\t\ted.view.dispatch(tr.scrollIntoView());\n\t\t\tconst ranges = chosen.map((m, i) => ({ from: tr.mapping.map(m.from, -1), to: ends[i] }));\n\t\t\tpresence.changed(ranges.filter((r) => r.from < r.to));\n\t\t\treturn {\n\t\t\t\treplaced: chosen.length,\n\t\t\t\ttotal: matches.length,\n\t\t\t\tranges,\n\t\t\t\tselection: selectionInfo(ed)\n\t\t\t};\n\t\t}\n\t};\n\n\tconst replaceRange: AgentTool = {\n\t\tname: 'replace_range',\n\t\ttitle: 'Replace a range',\n\t\tdescription:\n\t\t\t'Replaces the content between two document positions (from get_text `blocks` or find_text) with literal text. ' +\n\t\t\t'from === to inserts; an empty text deletes. Returns the new text\\'s `range` {from, to}.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tfrom: { type: 'integer', minimum: 0, description: 'Start document position.' },\n\t\t\t\tto: { type: 'integer', minimum: 0, description: 'End document position.' },\n\t\t\t\ttext: { type: 'string', description: 'Replacement text (newlines start new paragraphs).' }\n\t\t\t},\n\t\t\trequired: ['from', 'to', 'text'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Replace a range',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: async ({ from, to, text }, { signal }) => {\n\t\t\tconst ed = editor();\n\t\t\tcheckRange(ed, from, to);\n\t\t\tconst range = await presence.write(ed, from, to, text, signal);\n\t\t\treturn { replaced: { from, to }, range, selection: selectionInfo(ed), size: ed.state.doc.content.size };\n\t\t}\n\t};\n\n\tconst setSelection: AgentTool = {\n\t\tname: 'set_selection',\n\t\ttitle: 'Set the selection',\n\t\tdescription: 'Selects the content between two document positions (from === to places the cursor).',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tfrom: { type: 'integer', minimum: 0, description: 'Start document position.' },\n\t\t\t\tto: { type: 'integer', minimum: 0, description: 'End document position (default: from).' }\n\t\t\t},\n\t\t\trequired: ['from'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Set the selection',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ from, to = from }) => {\n\t\t\tconst ed = editor();\n\t\t\tcheckRange(ed, from, to);\n\t\t\tconst { doc } = ed.state;\n\t\t\ted.view.dispatch(ed.state.tr.setSelection(TextSelection.between(doc.resolve(from), doc.resolve(to))));\n\t\t\tconst selection = selectionInfo(ed);\n\t\t\tpresence.show(selection.from, selection.to);\n\t\t\treturn { selection };\n\t\t}\n\t};\n\n\tconst replaceSelection: AgentTool = {\n\t\tname: 'replace_selection',\n\t\ttitle: 'Replace selection',\n\t\tdescription:\n\t\t\t'Replaces the selected content with literal text (an empty string deletes the selection). ' +\n\t\t\t'Returns the new text\\'s `range` {from, to}.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: { text: { type: 'string', description: 'Replacement text.' } },\n\t\t\trequired: ['text'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Replace selection',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: async ({ text }, { signal }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst { from, to } = ed.state.selection;\n\t\t\tconst range = await presence.write(ed, from, to, text, signal);\n\t\t\treturn { replaced: { from, to }, range, selection: selectionInfo(ed) };\n\t\t}\n\t};\n\n\tconst editTable: AgentTool = {\n\t\tname: 'edit_table',\n\t\ttitle: 'Edit tables',\n\t\tdescription:\n\t\t\t'Inserts a table or edits the table at the cursor (or at `at`): add/delete rows and columns, delete the table, ' +\n\t\t\t'merge/split cells, toggle header row/column/cell, move between cells, set cell alignment or background. ' +\n\t\t\t'Returns the table size and the cursor cell afterwards.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\toperation: { type: 'string', enum: TABLE_ACTIONS.map((a) => a.action), description: 'Table operation.' },\n\t\t\t\tat: { type: 'integer', minimum: 0, description: 'Document position to place the cursor first (e.g. inside a cell).' },\n\t\t\t\trows: { type: 'integer', minimum: 1, maximum: 100, description: 'insertTable: rows (default 3).' },\n\t\t\t\tcols: { type: 'integer', minimum: 1, maximum: 50, description: 'insertTable: columns (default 3).' },\n\t\t\t\twithHeaderRow: { type: 'boolean', description: 'insertTable: first row is a header row (default true).' },\n\t\t\t\talign: {\n\t\t\t\t\ttype: ['string', 'null'],\n\t\t\t\t\tenum: ['left', 'center', 'right', 'justify', null],\n\t\t\t\t\tdescription: 'setCellAlignment: alignment, or null to clear.'\n\t\t\t\t},\n\t\t\t\tcolor: { type: ['string', 'null'], description: 'setCellBackground: CSS color, or null to clear.' }\n\t\t\t},\n\t\t\trequired: ['operation'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Edit tables',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: false,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ operation, at, ...params }) => {\n\t\t\tconst ed = editor();\n\t\t\tif (at !== undefined) {\n\t\t\t\tconst size = ed.state.doc.content.size;\n\t\t\t\tif (at > size) throw new Error(`Position ${at} is outside the document (size ${size}).`);\n\t\t\t\ted.view.dispatch(ed.state.tr.setSelection(TextSelection.near(ed.state.doc.resolve(at))));\n\t\t\t}\n\t\t\tconst action = operation as TableAction;\n\t\t\tconst state = getTableActionState(ed, action, params);\n\t\t\tif (state !== true) throw new Error(state);\n\t\t\trunTableAction(ed, action, params);\n\t\t\tconst table = getTableState(ed);\n\t\t\treturn {\n\t\t\t\toperation,\n\t\t\t\ttable: table.inTable\n\t\t\t\t\t? { rows: table.rows, columns: table.columns, row: table.row, column: table.column }\n\t\t\t\t\t: null,\n\t\t\t\tselection: selectionInfo(ed)\n\t\t\t};\n\t\t}\n\t};\n\n\treturn {\n\t\tid: options.id,\n\t\tnamespace: options.namespace,\n\t\ttools: () => {\n\t\t\tconst ed = editor();\n\t\t\tif (!ed || ed.isDestroyed) return [];\n\t\t\tif (!ed.isEditable) return [getText, findText, ...charts.read()];\n\t\t\tconst tools = [getText, findText, insertText, replaceText, replaceRange, replaceSelection, setSelection];\n\t\t\treturn [...tools, ...(isTableEnabled(ed) ? [editTable] : []), ...charts.read(), ...charts.write()];\n\t\t},\n\t\tsubscribe: (notify) => {\n\t\t\tconst ed = editor();\n\t\t\t// `setEditable` emits `update`; the registry ignores signals that don't change the list.\n\t\t\ted.on('update', notify);\n\t\t\ted.on('destroy', notify);\n\t\t\treturn () => {\n\t\t\t\ted.off('update', notify);\n\t\t\t\ted.off('destroy', notify);\n\t\t\t\tpresence.dispose();\n\t\t\t};\n\t\t}\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Spreadsheet provider\n// ---------------------------------------------------------------------------\n\nconst CELL_REF_REGEX = /^\\$?([A-Za-z]{1,3})\\$?(\\d{1,7})$/;\n\nfunction parseCell(ref: string): { col: number; row: number } {\n\tconst match = CELL_REF_REGEX.exec(ref.trim());\n\tif (!match) throw new Error(`Invalid cell reference \"${ref}\". Use A1 notation, e.g. \"B3\".`);\n\treturn { col: colNameToIndex(match[1].toUpperCase()), row: parseInt(match[2], 10) - 1 };\n}\n\nconst COLUMN_REGEX = /^[A-Za-z]{1,3}$/;\n\nfunction parseColumn(name: string): number {\n\tif (!COLUMN_REGEX.test(name.trim())) throw new Error(`Invalid column \"${name}\". Use a letter name, e.g. \"C\".`);\n\treturn colNameToIndex(name.trim().toUpperCase());\n}\n\nfunction parseRange(ref: string): CellRange {\n\tconst [startRef, endRef = startRef, extra] = String(ref).split(':');\n\tif (extra !== undefined) throw new Error(`Invalid range \"${ref}\". Use A1 notation, e.g. \"A1:C5\".`);\n\tconst start = parseCell(startRef);\n\tconst end = parseCell(endRef);\n\treturn normalizeRange({ startRow: start.row, startCol: start.col, endRow: end.row, endCol: end.col });\n}\n\nconst formatRange = (r: CellRange) =>\n\t`${indexToColName(r.startCol)}${r.startRow + 1}:${indexToColName(r.endCol)}${r.endRow + 1}`;\n\nconst CELL_INPUT = { type: ['string', 'number', 'boolean', 'null'] } as const;\n\n/**\n * Tools for a spreadsheet controller: `get_range` (A1 notation, values and raw\n * input), `set_cell` and `set_range` (raw input; values starting with `=` are\n * formulas; `set_range` writes a whole block as one undo step), and\n * `insert_rows` / `delete_rows` / `insert_columns` / `delete_columns`\n * (formula references shift like a user edit), and `merge_cells` /\n * `unmerge_cells` / `transpose_range` on an A1 range (not the user's\n * selection), checked against merges and read-only cells first.\n */\nexport function createSpreadsheetToolProvider(\n\tcontroller: SpreadsheetController,\n\toptions: ToolProviderOptions\n): AgentToolProvider {\n\tconst lookup = (row: number, col: number) => {\n\t\tconst rowDef = controller.document.rows[row];\n\t\tconst colDef = controller.document.columns[col];\n\t\treturn rowDef && colDef ? { rowDef, colDef } : null;\n\t};\n\n\tconst getRange: AgentTool = {\n\t\tname: 'get_range',\n\t\ttitle: 'Read cells',\n\t\tdescription:\n\t\t\t'Reads a cell range in A1 notation (e.g. \"A1:C5\" or \"B2\") and returns computed values, raw input, errors and the merged ranges that touch it (a merged range shows its value in its top-left cell).',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\trange: { type: 'string', description: 'A1-notation cell or range.' }\n\t\t\t},\n\t\t\trequired: ['range'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Read cells',\n\t\t\treadOnlyHint: true,\n\t\t\tdestructiveHint: false,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ range }) => {\n\t\t\tconst [startRef, endRef = startRef] = String(range).split(':');\n\t\t\tconst start = parseCell(startRef);\n\t\t\tconst end = parseCell(endRef);\n\t\t\tconst rowCount = controller.document.rows.length;\n\t\t\tconst colCount = controller.document.columns.length;\n\t\t\tconst r0 = Math.min(start.row, end.row);\n\t\t\tconst r1 = Math.min(Math.max(start.row, end.row), rowCount - 1);\n\t\t\tconst c0 = Math.min(start.col, end.col);\n\t\t\tconst c1 = Math.min(Math.max(start.col, end.col), colCount - 1);\n\t\t\tconst values: unknown[][] = [];\n\t\t\tconst raw: string[][] = [];\n\t\t\tconst errors: Array<{ cell: string; error: string }> = [];\n\t\t\tfor (let r = r0; r <= r1; r++) {\n\t\t\t\tconst valueRow: unknown[] = [];\n\t\t\t\tconst rawRow: string[] = [];\n\t\t\t\tfor (let c = c0; c <= c1; c++) {\n\t\t\t\t\tconst found = lookup(r, c)!;\n\t\t\t\t\tconst cell = controller.getCell(found.rowDef.id, found.colDef.id);\n\t\t\t\t\tvalueRow.push(cell.error ?? cell.value);\n\t\t\t\t\trawRow.push(cell.raw);\n\t\t\t\t\tconst error = cell.error ?? (isFormulaError(cell.value) ? cell.value : undefined);\n\t\t\t\t\tif (error) errors.push({ cell: `${indexToColName(c)}${r + 1}`, error });\n\t\t\t\t}\n\t\t\t\tvalues.push(valueRow);\n\t\t\t\traw.push(rawRow);\n\t\t\t}\n\t\t\tconst read: CellRange = { startRow: r0, startCol: c0, endRow: r1, endCol: c1 };\n\t\t\treturn {\n\t\t\t\trange: values.length && values[0].length ? formatRange(read) : null,\n\t\t\t\tvalues,\n\t\t\t\traw,\n\t\t\t\terrors,\n\t\t\t\tmerges: controller.getMerges().filter((m) => rangesIntersect(m, read)).map(formatRange),\n\t\t\t\tsheet: { rows: rowCount, columns: colCount }\n\t\t\t};\n\t\t}\n\t};\n\n\tconst setCell: AgentTool = {\n\t\tname: 'set_cell',\n\t\ttitle: 'Set a cell',\n\t\tdescription: 'Sets the raw input of one cell (A1 notation). Values starting with \"=\" are formulas.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tcell: { type: 'string', description: 'A1-notation cell, e.g. \"B3\".' },\n\t\t\t\tvalue: { type: ['string', 'number', 'boolean'], description: 'Raw cell input.' }\n\t\t\t},\n\t\t\trequired: ['cell', 'value'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Set a cell',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ cell, value }) => {\n\t\t\tconst { row, col } = parseCell(cell);\n\t\t\tconst found = lookup(row, col);\n\t\t\tif (!found) throw new Error(`Cell ${cell} is outside the sheet.`);\n\t\t\tconst { rowDef, colDef } = found;\n\t\t\tif (colDef.readOnly || controller.getCell(rowDef.id, colDef.id).readOnly || colDef.type === 'formula') {\n\t\t\t\tthrow new Error(`Cell ${cell} is read-only.`);\n\t\t\t}\n\t\t\tcontroller.setCellValue(rowDef.id, colDef.id, String(value));\n\t\t\tconst updated = controller.getCell(rowDef.id, colDef.id);\n\t\t\treturn { cell: cell.toUpperCase(), raw: updated.raw, value: updated.error ?? updated.value };\n\t\t}\n\t};\n\n\tconst isReadOnly = (row: number, col: number) => {\n\t\tconst found = lookup(row, col);\n\t\tif (!found) return false;\n\t\tconst { rowDef, colDef } = found;\n\t\treturn !!(colDef.readOnly || colDef.type === 'formula' || controller.getCell(rowDef.id, colDef.id).readOnly);\n\t};\n\n\tconst setRange: AgentTool = {\n\t\tname: 'set_range',\n\t\ttitle: 'Set a block of cells',\n\t\tdescription:\n\t\t\t'Writes a 2D block of raw inputs starting at a cell (A1 notation), row by row, as one undo step. ' +\n\t\t\t'Values starting with \"=\" are formulas; null or \"\" clears a cell. The sheet grows to fit. Fails without writing ' +\n\t\t\t'anything if any target cell is read-only.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tstart: { type: 'string', description: 'Top-left cell, e.g. \"A1\".' },\n\t\t\t\tvalues: {\n\t\t\t\t\ttype: 'array',\n\t\t\t\t\tminItems: 1,\n\t\t\t\t\tmaxItems: 10000,\n\t\t\t\t\titems: { type: 'array', items: CELL_INPUT, maxItems: 1000 },\n\t\t\t\t\tdescription: 'Rows of raw cell inputs, e.g. [[\"Name\", \"Total\"], [\"A\", \"=B3*2\"]].'\n\t\t\t\t}\n\t\t\t},\n\t\t\trequired: ['start', 'values'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: {\n\t\t\ttitle: 'Set a block of cells',\n\t\t\treadOnlyHint: false,\n\t\t\tdestructiveHint: true,\n\t\t\tidempotentHint: true,\n\t\t\topenWorldHint: false\n\t\t},\n\t\thandler: ({ start, values }) => {\n\t\t\tconst { row, col } = parseCell(start);\n\t\t\tconst rows = values as Array<Array<string | number | boolean | null>>;\n\t\t\tconst readOnly: string[] = [];\n\t\t\trows.forEach((line, r) =>\n\t\t\t\tline.forEach((_, c) => {\n\t\t\t\t\tif (isReadOnly(row + r, col + c)) readOnly.push(`${indexToColName(col + c)}${row + r + 1}`);\n\t\t\t\t})\n\t\t\t);\n\t\t\tif (readOnly.length) throw new Error(`Read-only cells: ${readOnly.slice(0, 20).join(', ')}. Nothing was written.`);\n\t\t\tcontroller.importRows(\n\t\t\t\trows.map((line) => line.map((v) => (v === null ? '' : String(v)))),\n\t\t\t\t{ startRow: row, startCol: col }\n\t\t\t);\n\t\t\tconst width = Math.max(0, ...rows.map((line) => line.length));\n\t\t\tconst end = `${indexToColName(col + Math.max(width, 1) - 1)}${row + rows.length}`;\n\t\t\tconst errors: Array<{ cell: string; error: string }> = [];\n\t\t\trows.forEach((line, r) =>\n\t\t\t\tline.forEach((_, c) => {\n\t\t\t\t\tconst found = lookup(row + r, col + c);\n\t\t\t\t\tif (!found) return;\n\t\t\t\t\tconst cell = controller.getCell(found.rowDef.id, found.colDef.id);\n\t\t\t\t\tconst error = cell.error ?? (isFormulaError(cell.value) ? cell.value : undefined);\n\t\t\t\t\tif (error) errors.push({ cell: `${indexToColName(col + c)}${row + r + 1}`, error: String(error) });\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn {\n\t\t\t\trange: `${indexToColName(col)}${row + 1}:${end}`,\n\t\t\t\twritten: rows.reduce((n, line) => n + line.length, 0),\n\t\t\t\terrors,\n\t\t\t\tsheet: { rows: controller.document.rows.length, columns: controller.document.columns.length }\n\t\t\t};\n\t\t}\n\t};\n\n\tconst COUNT = { type: 'integer', minimum: 1, maximum: 1000, description: 'How many (default 1).' } as const;\n\tconst sheetSize = () => ({ rows: controller.document.rows.length, columns: controller.document.columns.length });\n\tconst structural = (\n\t\tname: string,\n\t\ttitle: string,\n\t\tdescription: string,\n\t\tproperties: Record<string, unknown>,\n\t\trequired: string[],\n\t\tdestructive: boolean,\n\t\thandler: AgentTool['handler']\n\t): AgentTool => ({\n\t\tname,\n\t\ttitle,\n\t\tdescription,\n\t\tinputSchema: { type: 'object', properties: properties as never, required, additionalProperties: false },\n\t\tannotations: { title, readOnlyHint: false, destructiveHint: destructive, idempotentHint: false, openWorldHint: false },\n\t\thandler\n\t});\n\n\tconst insertRows = structural(\n\t\t'insert_rows',\n\t\t'Insert rows',\n\t\t'Inserts empty rows before a row number (1-based; one past the last row appends). References below shift down.',\n\t\t{ before: { type: 'integer', minimum: 1, description: 'Row number to insert before.' }, count: COUNT },\n\t\t['before'],\n\t\tfalse,\n\t\t({ before, count = 1 }) => {\n\t\t\tconst index = before - 1;\n\t\t\tif (index > controller.document.rows.length) throw new Error(`Row ${before} is past the end of the sheet (${controller.document.rows.length} rows).`);\n\t\t\tfor (let i = 0; i < count; i++) controller.insertRow(index);\n\t\t\treturn { inserted: count, rows: `${before}:${before + count - 1}`, sheet: sheetSize() };\n\t\t}\n\t);\n\tconst deleteRows = structural(\n\t\t'delete_rows',\n\t\t'Delete rows',\n\t\t'Deletes rows starting at a row number (1-based) with their contents. References below shift up; references to deleted cells become #REF!.',\n\t\t{ start: { type: 'integer', minimum: 1, description: 'First row number to delete.' }, count: COUNT },\n\t\t['start'],\n\t\ttrue,\n\t\t({ start, count = 1 }) => {\n\t\t\tconst rows = controller.document.rows.slice(start - 1, start - 1 + count);\n\t\t\tif (rows.length < count) throw new Error(`Rows ${start}-${start + count - 1} are not all in the sheet (${controller.document.rows.length} rows).`);\n\t\t\tfor (const row of rows) controller.deleteRow(row.id);\n\t\t\treturn { deleted: count, sheet: sheetSize() };\n\t\t}\n\t);\n\tconst insertColumns = structural(\n\t\t'insert_columns',\n\t\t'Insert columns',\n\t\t'Inserts empty columns before a column letter (one past the last column appends). References to the right shift.',\n\t\t{ before: { type: 'string', description: 'Column letter to insert before, e.g. \"C\".' }, count: COUNT },\n\t\t['before'],\n\t\tfalse,\n\t\t({ before, count = 1 }) => {\n\t\t\tconst index = parseColumn(before);\n\t\t\tif (index > controller.document.columns.length) throw new Error(`Column ${before} is past the end of the sheet (${controller.document.columns.length} columns).`);\n\t\t\tfor (let i = 0; i < count; i++) controller.insertColumn(index);\n\t\t\treturn { inserted: count, columns: `${indexToColName(index)}:${indexToColName(index + count - 1)}`, sheet: sheetSize() };\n\t\t}\n\t);\n\tconst deleteColumns = structural(\n\t\t'delete_columns',\n\t\t'Delete columns',\n\t\t'Deletes columns starting at a column letter with their contents. References to the right shift left; references to deleted cells become #REF!.',\n\t\t{ start: { type: 'string', description: 'First column letter to delete, e.g. \"C\".' }, count: COUNT },\n\t\t['start'],\n\t\ttrue,\n\t\t({ start, count = 1 }) => {\n\t\t\tconst index = parseColumn(start);\n\t\t\tconst columns = controller.document.columns.slice(index, index + count);\n\t\t\tif (columns.length < count) throw new Error(`Columns from ${start} (${count}) are not all in the sheet (${controller.document.columns.length} columns).`);\n\t\t\tfor (const column of columns) controller.deleteColumn(column.id);\n\t\t\treturn { deleted: count, sheet: sheetSize() };\n\t\t}\n\t);\n\n\tconst RANGE = { type: 'string', description: 'A1-notation range of at least two cells, e.g. \"A1:C1\".' } as const;\n\tconst inSheet = (r: CellRange, ref: string) => {\n\t\tif (r.endRow >= controller.document.rows.length || r.endCol >= controller.document.columns.length) {\n\t\t\tthrow new Error(`Range ${ref} is outside the sheet (${controller.document.rows.length} rows, ${controller.document.columns.length} columns).`);\n\t\t}\n\t};\n\tconst readOnlyIn = (r: CellRange) => {\n\t\tconst cells: string[] = [];\n\t\tfor (let row = r.startRow; row <= r.endRow; row++) {\n\t\t\tfor (let col = r.startCol; col <= r.endCol; col++) if (isReadOnly(row, col)) cells.push(`${indexToColName(col)}${row + 1}`);\n\t\t}\n\t\treturn cells;\n\t};\n\tconst SPILL = 'it overlaps the spill range of a dynamic array formula; move or clear that formula first';\n\n\tconst mergeCells = structural(\n\t\t'merge_cells',\n\t\t'Merge cells',\n\t\t'Merges a range into one cell showing the top-left cell\\'s value. The other cells in the range are cleared, so move their contents first. ' +\n\t\t\t'Merges fully inside the range are absorbed. One undo step. Returns the merged range and the cells that were cleared. Use merges only for titles outside data regions: they break sorting, filtering and transposing.',\n\t\t{ range: RANGE },\n\t\t['range'],\n\t\ttrue,\n\t\t({ range }) => {\n\t\t\tconst r = parseRange(range);\n\t\t\tinSheet(r, range);\n\t\t\tif (isSingleCellRange(r)) throw new Error(`${range} is a single cell; give a range of at least two cells.`);\n\t\t\tconst partial = controller.getMerges().find((m) => rangesIntersect(m, r) && !rangeContainsRange(r, m));\n\t\t\tif (partial) throw new Error(`${range} partly overlaps the merged range ${formatRange(partial)}; include all of it or unmerge it first.`);\n\t\t\tconst readOnly = readOnlyIn(r);\n\t\t\tif (readOnly.length) throw new Error(`Read-only cells: ${readOnly.slice(0, 20).join(', ')}. Nothing was merged.`);\n\t\t\tconst cleared: string[] = [];\n\t\t\tfor (let row = r.startRow; row <= r.endRow; row++) {\n\t\t\t\tfor (let col = r.startCol; col <= r.endCol; col++) {\n\t\t\t\t\tif (row === r.startRow && col === r.startCol) continue;\n\t\t\t\t\tconst { rowDef, colDef } = lookup(row, col)!;\n\t\t\t\t\tif (controller.getCell(rowDef.id, colDef.id).raw !== '') cleared.push(`${indexToColName(col)}${row + 1}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!controller.mergeCells(r)) throw new Error(`Cannot merge ${range}: ${SPILL}.`);\n\t\t\treturn { merged: formatRange(r), cleared };\n\t\t}\n\t);\n\tconst unmergeCells = structural(\n\t\t'unmerge_cells',\n\t\t'Unmerge cells',\n\t\t'Splits every merged range that touches a range back into separate cells (the value stays in the top-left cell; the others are empty). One undo step. Returns the ranges that were unmerged.',\n\t\t{ range: { type: 'string', description: 'A1-notation cell or range; every merge touching it is split, e.g. \"A1\".' } },\n\t\t['range'],\n\t\tfalse,\n\t\t({ range }) => {\n\t\t\tconst r = parseRange(range);\n\t\t\tconst touched = controller.getMerges().filter((m) => rangesIntersect(m, r));\n\t\t\tif (!touched.length) throw new Error(`No merged cells in ${range}. get_range lists the merges in a range.`);\n\t\t\tcontroller.unmergeCells(r);\n\t\t\treturn { unmerged: touched.map(formatRange) };\n\t\t}\n\t);\n\tconst transposeRange = structural(\n\t\t'transpose_range',\n\t\t'Transpose a range',\n\t\t'Swaps the rows and columns of a range in place, anchored on its top-left cell (A1:C2 becomes A1:B3), moving values, formulas, formats and rules; ' +\n\t\t\t'formulas that point into the range follow their cells. The sheet grows to fit. One undo step. Returns the new range. Cells the result covers outside the original range are overwritten.',\n\t\t{ range: RANGE },\n\t\t['range'],\n\t\ttrue,\n\t\t({ range }) => {\n\t\t\tconst s = parseRange(range);\n\t\t\tinSheet(s, range);\n\t\t\tif (isSingleCellRange(s)) throw new Error(`${range} is a single cell; give a range of at least two cells.`);\n\t\t\tconst t: CellRange = {\n\t\t\t\tstartRow: s.startRow,\n\t\t\t\tstartCol: s.startCol,\n\t\t\t\tendRow: s.startRow + (s.endCol - s.startCol),\n\t\t\t\tendCol: s.startCol + (s.endRow - s.startRow)\n\t\t\t};\n\t\t\tconst merge = controller.getMerges().find((m) => rangesIntersect(m, s) || rangesIntersect(m, t));\n\t\t\tif (merge) throw new Error(`Cannot transpose ${range}: the merged range ${formatRange(merge)} is in the way; unmerge it first.`);\n\t\t\tconst readOnly = [...new Set([...readOnlyIn(s), ...readOnlyIn(t)])];\n\t\t\tif (readOnly.length) throw new Error(`Read-only cells: ${readOnly.slice(0, 20).join(', ')}. Nothing was transposed.`);\n\t\t\tif (!controller.transposeRange(s)) throw new Error(`Cannot transpose ${range}: ${SPILL}.`);\n\t\t\treturn { range: formatRange(t), sheet: sheetSize() };\n\t\t}\n\t);\n\n\treturn {\n\t\tid: options.id,\n\t\tnamespace: options.namespace,\n\t\ttools: [getRange, setCell, setRange, insertRows, deleteRows, insertColumns, deleteColumns, mergeCells, unmergeCells, transposeRange]\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Static editor tools (tools.ts) as a provider\n// ---------------------------------------------------------------------------\n\nexport interface EditorMcpToolProviderOptions<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n> extends ToolProviderOptions {\n\t/** Current editor / canvas state. */\n\tgetSnapshot: () => EditorSnapshot<TNode, TEdge>;\n\t/** Applies planned steps (e.g. via your canvas runes / editor controller). */\n\tapplySteps: (steps: AgentStep<TNode, TEdge>[]) => void | Promise<void>;\n\t/** Optional `view_editor` implementation; the tool is only exposed when provided. */\n\tcaptureView?: (args: { scope?: 'viewport' | 'canvas'; format?: 'jpeg' | 'png'; maxEdge?: number }) =>\n\t\t| Promise<{ data: string; mimeType: string }>\n\t\t| { data: string; mimeType: string };\n\t/** Options forwarded to the planner. */\n\tplannerOptions?: Parameters<typeof planToolCall>[3];\n\t/**\n\t * `split` (default): one strictly typed tool per operation (`canvas_add_node`,\n\t * `canvas_connect`, ..., `prompt_insert_text`, ...). `combined`: the\n\t * `edit_canvas` / `edit_prompt` tools with an `operation` argument.\n\t */\n\ttoolStyle?: 'split' | 'combined';\n\t/** Which edit tools to expose (default both). */\n\tsurfaces?: { canvas?: boolean; prompt?: boolean };\n}\n\n/** Describes which arguments a combined `edit_*` call is missing. */\nfunction missingArgumentsMessage(name: 'edit_canvas' | 'edit_prompt', args: Record<string, any>): string {\n\tconst required = EDIT_OPERATION_REQUIRED_ARGUMENTS[name][args.operation as string];\n\tif (!required) return `Unknown operation \"${args.operation}\".`;\n\tconst missing = required.filter((key) =>\n\t\tkey === 'id' ? args.id === undefined && args.node?.id === undefined : args[key] === undefined\n\t);\n\treturn missing.length\n\t\t? `Operation \"${args.operation}\" requires ${required.join(', ')}; missing: ${missing.join(', ')}.`\n\t\t: `Operation \"${args.operation}\" has invalid arguments (expected ${required.join(', ')} with the types in the schema).`;\n}\n\n/** What a set of applied steps created, so the agent can refer to it next. */\nfunction describeSteps<TNode extends CanvasNode, TEdge extends CanvasEdge>(steps: AgentStep<TNode, TEdge>[]) {\n\tconst nodes: Array<{ id: string; position: { x: number; y: number } }> = [];\n\tconst edges: Array<{ id: string; source: string; target: string }> = [];\n\tfor (const step of steps) {\n\t\tif (step.type === 'step:canvas_add_node') nodes.push({ id: step.node.id, position: step.node.position });\n\t\tif (step.type === 'step:canvas_add_edge') edges.push({ id: step.edge.id, source: step.edge.source, target: step.edge.target });\n\t}\n\treturn {\n\t\tapplied: steps.length,\n\t\tsteps: steps.map((s) => s.type),\n\t\t...(nodes.length || edges.length ? { created: { nodes, edges } } : {})\n\t};\n}\n\n/**\n * Exposes the editor MCP tools (`get_editor_state`, the canvas and prompt\n * edit tools, and `view_editor` when `captureView` is given) through a\n * `ToolRegistry`. Edits are checked against the current snapshot\n * (`validateAgentCommand`: unknown node ids, duplicate ids, unordered ranges\n * are tool errors), planned with the planner and handed to `applySteps`.\n * Results list the ids of created nodes and edges.\n */\nexport function createEditorMcpToolProvider<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(options: EditorMcpToolProviderOptions<TNode, TEdge>): AgentToolProvider {\n\tconst edit = (name: string) => async (args: Record<string, any>) => {\n\t\tconst command = toolCallToAgentCommand<TNode, TEdge>(name, args);\n\t\tif (!command) {\n\t\t\tthrow new Error(\n\t\t\t\tname === 'edit_canvas' || name === 'edit_prompt'\n\t\t\t\t\t? missingArgumentsMessage(name, args)\n\t\t\t\t\t: `Invalid arguments for \"${name}\".`\n\t\t\t);\n\t\t}\n\t\tconst snapshot = options.getSnapshot();\n\t\tconst problem = validateAgentCommand(command, snapshot);\n\t\tif (problem) throw new Error(problem);\n\t\tconst steps = planAgentCommand(command, snapshot, options.plannerOptions);\n\t\tawait options.applySteps(steps);\n\t\treturn describeSteps(steps);\n\t};\n\n\tconst canvas = options.surfaces?.canvas ?? true;\n\tconst prompt = options.surfaces?.prompt ?? true;\n\tconst editTools =\n\t\toptions.toolStyle === 'combined'\n\t\t\t? [...(canvas ? [MCP_TOOL_EDIT_CANVAS] : []), ...(prompt ? [MCP_TOOL_EDIT_PROMPT] : [])]\n\t\t\t: [...(canvas ? EDITOR_CANVAS_TOOLS : []), ...(prompt ? EDITOR_PROMPT_TOOLS : [])];\n\n\tconst tools: AgentTool[] = [\n\t\t{\n\t\t\t...MCP_TOOL_GET_EDITOR_STATE,\n\t\t\thandler: ({ includeDocument = true, includeCanvas = true }) => {\n\t\t\t\tconst snapshot = options.getSnapshot();\n\t\t\t\treturn {\n\t\t\t\t\t...(includeDocument && snapshot.document ? { document: snapshot.document } : {}),\n\t\t\t\t\t...(includeCanvas && snapshot.canvas ? { canvas: snapshot.canvas } : {})\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t\t...editTools.map((definition) => ({ ...definition, handler: edit(definition.name) }))\n\t] as AgentTool[];\n\n\tif (options.captureView) {\n\t\tconst capture = options.captureView;\n\t\ttools.push({\n\t\t\t...MCP_TOOL_VIEW_EDITOR,\n\t\t\thandler: async (args) => {\n\t\t\t\tconst image = await capture(args);\n\t\t\t\treturn { content: [{ type: 'image', data: image.data, mimeType: image.mimeType }] };\n\t\t\t}\n\t\t} as AgentTool);\n\t}\n\n\treturn { id: options.id, namespace: options.namespace, tools };\n}\n","import type { Editor } from '@tiptap/core';\nimport { closeHistory } from '@tiptap/pm/history';\nimport type { Node as ProsemirrorNode } from '@tiptap/pm/model';\nimport { getDocumentCharts, type DocumentChart } from '../extensions/chart/chart-node.js';\nimport { MAX_SPARKLINE_VALUES, SPARKLINE_KINDS, normalizeSparklineValues, sparklineLabel } from '../extensions/chart/sparkline-node.js';\nimport { CHART_UPDATE_PROPERTIES, SERIES_PROPS, VALUE } from '../chart/schemas.js';\nimport { normalizeChartSpec, parseChartValue } from '../chart/spec.js';\nimport { describeChart } from '../chart/summary.js';\nimport type { ChartSpec } from '../chart/types.js';\nimport type { AgentTool } from '../tools/registry.js';\nimport type { JsonSchema } from '../tools/schema.js';\n\nconst READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\nconst WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false } as const;\nconst WRITE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\n\nconst SIZE_WIDTH: JsonSchema = {\n\ttype: ['number', 'null'],\n\tminimum: 50,\n\tmaximum: 4096,\n\tdescription: 'Width in pixels, or null to fill the line (default null).'\n};\nconst SIZE_HEIGHT: JsonSchema = { type: 'number', minimum: 50, maximum: 4096, description: 'Height in pixels (default 320).' };\n\n/** The chart data and presentation fields an agent can pass (a `ChartSpec` without `version`). */\nconst SPEC_PROPERTIES: Record<string, JsonSchema> = {\n\t...CHART_UPDATE_PROPERTIES,\n\tcategories: { type: 'array', items: { type: 'string' }, description: 'Category labels in order (x axis labels, or pie slices).' },\n\tseries: {\n\t\ttype: 'array',\n\t\tdescription: 'The series, each with one value per category (padded with gaps).',\n\t\titems: {\n\t\t\ttype: 'object',\n\t\t\tproperties: { id: { type: 'string', description: 'Stable id (default: from the name).' }, ...SERIES_PROPS },\n\t\t\trequired: ['name', 'values'],\n\t\t\tadditionalProperties: false\n\t\t}\n\t}\n};\n\n/** Drops `null` from series fields (null means \"use the default\" in the tool schema). */\nfunction cleanSeries(series: unknown): unknown {\n\tif (!Array.isArray(series)) return series;\n\treturn series.map((s) => (s && typeof s === 'object' ? Object.fromEntries(Object.entries(s).filter(([, v]) => v !== null)) : s));\n}\n\n/** Normalizes agent input as a chart, throwing every problem as one sentence list. */\nfunction checkedSpec(input: Record<string, unknown>): ChartSpec {\n\tconst { spec, issues } = normalizeChartSpec({ ...input, ...(input.series !== undefined ? { series: cleanSeries(input.series) } : {}) });\n\tif (issues.length) throw new Error(`The chart was not changed: ${issues.map((i) => i.message).join(' ')}`);\n\treturn spec;\n}\n\nfunction describe(chart: DocumentChart, includeSpec: boolean) {\n\tconst { spec, node } = chart;\n\treturn {\n\t\tid: chart.id,\n\t\tpos: chart.pos,\n\t\ttype: spec.type,\n\t\ttitle: spec.title,\n\t\tcategories: spec.categories,\n\t\tseries: spec.series.map((s) => ({ id: s.id, name: s.name })),\n\t\twidth: node.attrs.width ?? null,\n\t\theight: node.attrs.height,\n\t\tdescription: describeChart(spec),\n\t\t...(includeSpec ? { spec } : {})\n\t};\n}\n\nfunction sparklines(doc: ProsemirrorNode) {\n\tconst found: { pos: number; kind: string; values: (number | null)[]; label: string }[] = [];\n\tdoc.descendants((node, pos) => {\n\t\tif (node.type.name === 'sparkline') found.push({ pos, kind: node.attrs.kind, values: node.attrs.values, label: sparklineLabel(node) });\n\t});\n\treturn found;\n}\n\nfunction checkPosition(editor: Editor, at: number | undefined) {\n\tconst size = editor.state.doc.content.size;\n\tif (at !== undefined && at > size) throw new Error(`Position ${at} is outside the document (size ${size}).`);\n}\n\n/**\n * Chart tools for a rich-text editor whose schema has the `chart` node:\n * `list_document_charts` (read), `insert_chart` and `update_chart_in_document` (writes), plus\n * `insert_sparkline` when the schema has `sparkline`. `createRichTextToolProvider` adds them.\n */\nexport function createDocumentChartTools(editor: () => Editor): { read: () => AgentTool[]; write: () => AgentTool[] } {\n\tconst chartType = () => editor().schema.nodes.chart;\n\tconst findChart = (args: { chartId?: string; pos?: number }): DocumentChart => {\n\t\tconst charts = getDocumentCharts(editor().state.doc);\n\t\tconst list = charts.map((c) => `\"${c.id}\" at ${c.pos}`).join(', ') || 'none; add one with insert_chart';\n\t\tif (args.chartId !== undefined) {\n\t\t\tconst chart = charts.find((c) => c.id === args.chartId);\n\t\t\tif (!chart) throw new Error(`There is no chart \"${args.chartId}\" in the document. Charts: ${list}.`);\n\t\t\tif (args.pos !== undefined && args.pos !== chart.pos) throw new Error(`Chart \"${args.chartId}\" is at ${chart.pos}, not ${args.pos}; pass one of chartId or pos.`);\n\t\t\treturn chart;\n\t\t}\n\t\tif (args.pos !== undefined) {\n\t\t\tconst chart = charts.find((c) => c.pos === args.pos);\n\t\t\tif (!chart) throw new Error(`There is no chart at position ${args.pos}. Charts: ${list}.`);\n\t\t\treturn chart;\n\t\t}\n\t\tthrow new Error(`Pass chartId or pos (from list_document_charts). Charts: ${list}.`);\n\t};\n\n\tconst listCharts: AgentTool = {\n\t\tname: 'list_document_charts',\n\t\ttitle: 'List charts in the document',\n\t\tdescription:\n\t\t\t'Lists the charts in the document in order: id, document position, type, title, categories, series (ids and names), size in pixels and a plain-language description of the data. Also lists inline sparklines (position, kind, values). Pass the id or position to update_chart_in_document.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: { includeSpec: { type: 'boolean', description: 'Also return each chart\\'s full spec (default false).' } },\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: { title: 'List charts in the document', ...READ },\n\t\thandler: ({ includeSpec = false }) => {\n\t\t\tconst doc = editor().state.doc;\n\t\t\treturn { charts: getDocumentCharts(doc).map((c) => describe(c, includeSpec)), sparklines: editor().schema.nodes.sparkline ? sparklines(doc) : [] };\n\t\t}\n\t};\n\n\tconst insertChart: AgentTool = {\n\t\tname: 'insert_chart',\n\t\ttitle: 'Insert a chart',\n\t\tdescription:\n\t\t\t'Inserts a chart block at a document position (or the cursor), as one undo step. Give the categories and series (one value per category) and any presentation fields (type, titles, axes, legend, stacking, palette, …). Invalid fields change nothing and come back as sentences. Returns the new chart\\'s id, position and stored spec.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tspec: { type: 'object', description: 'The chart: categories and series, plus optional presentation fields.', properties: SPEC_PROPERTIES, required: ['categories', 'series'], additionalProperties: false },\n\t\t\t\tat: { type: 'integer', minimum: 0, description: 'Document position (default: the selection). A position inside a paragraph splits it.' },\n\t\t\t\twidth: SIZE_WIDTH,\n\t\t\t\theight: SIZE_HEIGHT\n\t\t\t},\n\t\t\trequired: ['spec'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: { title: 'Insert a chart', ...WRITE },\n\t\thandler: ({ spec: input, at, width = null, height }) => {\n\t\t\tconst ed = editor();\n\t\t\tcheckPosition(ed, at);\n\t\t\tconst spec = checkedSpec(input);\n\t\t\tconst id = (ed.extensionManager.extensions.find((e) => e.name === 'chart')?.options.generateId as (() => string) | undefined)?.() ?? `chart-${Date.now().toString(36)}`;\n\t\t\tconst node = { type: 'chart', attrs: { id, spec, width, ...(height !== undefined ? { height } : {}) } };\n\t\t\t// Each tool call is its own undo step, however quickly calls follow each other.\n\t\t\tconst chain = ed.chain().command(({ tr }) => (closeHistory(tr), true));\n\t\t\tconst ok = (at !== undefined ? chain.insertContentAt(at, node) : chain.insertContent(node)).run();\n\t\t\tconst chart = getDocumentCharts(ed.state.doc).find((c) => c.id === id);\n\t\t\tif (!ok || !chart) throw new Error(`A chart cannot go at ${at ?? ed.state.selection.from}; pass a position between blocks or inside a paragraph.`);\n\t\t\treturn { id: chart.id, pos: chart.pos, spec: chart.spec, width: chart.node.attrs.width, height: chart.node.attrs.height };\n\t\t}\n\t};\n\n\tconst updateChart: AgentTool = {\n\t\tname: 'update_chart_in_document',\n\t\ttitle: 'Change a chart in the document',\n\t\tdescription:\n\t\t\t'Changes one chart (by id or position from list_document_charts) as one undo step: data (categories, series — given series replace all) and / or presentation fields, which merge into the current spec (axes and legend field by field), plus its size. All-or-nothing: invalid values change nothing and come back as sentences. Returns the chart\\'s id, position and new spec.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tchartId: { type: 'string', description: 'Chart id from list_document_charts.' },\n\t\t\t\tpos: { type: 'integer', minimum: 0, description: 'Document position of the chart (from list_document_charts), instead of chartId.' },\n\t\t\t\tchanges: { type: 'object', description: 'Fields to change; omitted fields keep their value.', properties: SPEC_PROPERTIES, additionalProperties: false },\n\t\t\t\twidth: SIZE_WIDTH,\n\t\t\t\theight: { ...SIZE_HEIGHT, description: 'Height in pixels (default: unchanged).' }\n\t\t\t},\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: { title: 'Change a chart in the document', ...WRITE_IDEMPOTENT },\n\t\thandler: ({ chartId, pos, changes, width, height }) => {\n\t\t\tconst ed = editor();\n\t\t\tconst chart = findChart({ chartId, pos });\n\t\t\tif (!changes && width === undefined && height === undefined) throw new Error('Nothing to change: pass changes, width or height, e.g. {\"changes\": {\"type\": \"line\"}}.');\n\t\t\tconst s = chart.spec;\n\t\t\tconst patch = (changes ?? {}) as Record<string, any>;\n\t\t\tconst spec = checkedSpec({\n\t\t\t\t...s,\n\t\t\t\t...patch,\n\t\t\t\txAxis: patch.xAxis ? { ...s.xAxis, ...patch.xAxis } : s.xAxis,\n\t\t\t\tyAxis: patch.yAxis ? { ...s.yAxis, ...patch.yAxis } : s.yAxis,\n\t\t\t\tyAxis2: patch.yAxis2 ? { ...s.yAxis2, ...patch.yAxis2 } : s.yAxis2,\n\t\t\t\tlegend: patch.legend ? { ...s.legend, ...patch.legend } : s.legend\n\t\t\t});\n\t\t\tconst attrs = { ...chart.node.attrs, spec, ...(width !== undefined ? { width } : {}), ...(height !== undefined ? { height } : {}) };\n\t\t\ted.view.dispatch(closeHistory(ed.state.tr.setNodeMarkup(chart.pos, undefined, attrs)));\n\t\t\treturn { id: chart.id, pos: chart.pos, spec, width: attrs.width, height: attrs.height };\n\t\t}\n\t};\n\n\tconst insertSparkline: AgentTool = {\n\t\tname: 'insert_sparkline',\n\t\ttitle: 'Insert a sparkline',\n\t\tdescription: 'Inserts a word-sized chart into the text at a document position (or the cursor), as one undo step. Returns its position and stored values.',\n\t\tinputSchema: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tvalues: { type: 'array', items: VALUE, minItems: 1, maxItems: MAX_SPARKLINE_VALUES, description: 'Values in order (null for a gap).' },\n\t\t\t\tkind: { type: 'string', enum: [...SPARKLINE_KINDS], description: 'line (default), column, or win-loss (sign only).' },\n\t\t\t\tat: { type: 'integer', minimum: 0, description: 'Document position inside a paragraph (default: the selection).' },\n\t\t\t\twidth: { type: 'number', minimum: 4, maximum: 1000, description: 'Width in pixels (default 80).' },\n\t\t\t\theight: { type: 'number', minimum: 4, maximum: 200, description: 'Height in pixels (default 18).' },\n\t\t\t\tcolor: { type: 'string', description: 'CSS colour (default: the first palette colour).' },\n\t\t\t\tlabel: { type: 'string', description: 'Accessible label (default: \"Trend from <first> to <last>\").' }\n\t\t\t},\n\t\t\trequired: ['values'],\n\t\t\tadditionalProperties: false\n\t\t},\n\t\tannotations: { title: 'Insert a sparkline', ...WRITE },\n\t\thandler: ({ values, kind = 'line', at, width = 80, height = 18, color = null, label = null }) => {\n\t\t\tconst ed = editor();\n\t\t\tcheckPosition(ed, at);\n\t\t\tconst parsed = (values as unknown[]).map((v) => parseChartValue(v));\n\t\t\tconst bad = parsed.findIndex((v) => v === undefined);\n\t\t\tif (bad >= 0) throw new Error(`values[${bad}] (\"${String(values[bad])}\") is not a number; pass numbers, numeric text or null. Nothing was inserted.`);\n\t\t\tconst pos = at ?? ed.state.selection.from;\n\t\t\tif (!ed.state.doc.resolve(pos).parent.inlineContent) throw new Error(`Position ${pos} is between blocks; a sparkline goes inside a paragraph (use a position from get_text {blocks: true}).`);\n\t\t\tconst stored = normalizeSparklineValues(parsed);\n\t\t\ted.chain().command(({ tr }) => (closeHistory(tr), true)).insertContentAt(pos, { type: 'sparkline', attrs: { values: stored, kind, width, height, color, label } }).run();\n\t\t\treturn { pos, kind, values: stored };\n\t\t}\n\t};\n\n\treturn {\n\t\tread: () => (chartType() ? [listCharts] : []),\n\t\twrite: () => (chartType() ? [insertChart, updateChart, ...(editor().schema.nodes.sparkline ? [insertSparkline] : [])] : [])\n\t};\n}\n","import { hasVariantCommands, type ComponentCommandTarget } from '../commands/builtins.js';\nimport type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport type { ToolProviderOptions } from './providers.js';\n\nconst IDS = { type: 'array', items: { type: 'string' }, description: 'Node ids (default: the selection).' } as const;\nconst ID = { type: 'string', description: 'An instance, or a layer inside one (default: the first selected node).' } as const;\n\nconst COMPONENT_ID = { type: 'string', description: 'Component or component set id (from list_components).' } as const;\nconst PARENT_ID = { type: 'string', description: 'Frame to place the instance in (default: the canvas root).' } as const;\nconst X = { type: 'number', description: 'X position (canvas coordinates, or relative to the parent frame).' } as const;\nconst Y = { type: 'number', description: 'Y position (canvas coordinates, or relative to the parent frame).' } as const;\n\nconst READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\nconst WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false } as const;\nconst DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false } as const;\n\n/** Tool titles shown by MCP clients. */\nconst TITLES: Record<string, string> = {\n\tlist_components: 'List components',\n\tinspect_instance: 'Inspect an instance',\n\tcreate_component: 'Create a component',\n\tcombine_as_variants: 'Combine as variants',\n\tcreate_instance: 'Create an instance',\n\tset_instance_properties: 'Set instance properties',\n\tswap_instance: 'Swap an instance',\n\treset_overrides: 'Reset overrides',\n\tpush_overrides: 'Push overrides to the main component',\n\tdetach_instance: 'Detach instances',\n\tgo_to_main_component: 'Go to the main component',\n\trestore_main_component: 'Restore the main component',\n\tadd_variant: 'Add a variant',\n\tremove_variant: 'Remove a variant',\n\tset_variant_properties: 'Set variant properties',\n\trename_variant_property: 'Rename a variant property',\n\trename_variant_value: 'Rename a variant value'\n};\n\n/** Tools that discard state the agent cannot get back by calling another tool. */\nconst DESTRUCTIVE_TOOLS = new Set(['reset_overrides', 'push_overrides', 'detach_instance', 'remove_variant']);\n\ntype Outcome = { ok: true; ids: string[] } | { ok: false; reason: string };\nconst check = (result: Outcome) => {\n\tif (!result.ok) throw new Error(result.reason);\n\treturn { ids: result.ids };\n};\n\n/**\n * Agent tools for canvas components and instances, backed by a components controller\n * (`createCanvasComponentsController`): `list_components`, `inspect_instance`,\n * `create_component`, `combine_as_variants`, `create_instance`, `set_instance_properties`\n * (variants and component properties), `swap_instance`, `reset_overrides`, `push_overrides`,\n * `detach_instance`, `go_to_main_component`, `restore_main_component` and (when the controller\n * has them) the variant editors `add_variant`, `remove_variant`, `set_variant_properties`,\n * `rename_variant_property` and `rename_variant_value`. Refused actions come back as tool errors\n * carrying the reason.\n */\nexport function createCanvasComponentToolProvider(\n\tcontroller: ComponentCommandTarget,\n\toptions: ToolProviderOptions = { id: 'canvas-components' }\n): AgentToolProvider {\n\tconst tool = (\n\t\tname: string,\n\t\tdescription: string,\n\t\tproperties: Record<string, unknown>,\n\t\thandler: AgentTool['handler'],\n\t\textra: { required?: string[]; readOnly?: boolean } = {}\n\t): AgentTool => ({\n\t\tname,\n\t\ttitle: TITLES[name],\n\t\tdescription,\n\t\tinputSchema: { type: 'object', properties: properties as never, ...(extra.required ? { required: extra.required } : {}), additionalProperties: false },\n\t\tannotations: {\n\t\t\ttitle: TITLES[name],\n\t\t\t...(extra.readOnly ? READ : DESTRUCTIVE_TOOLS.has(name) ? DESTRUCTIVE : WRITE)\n\t\t},\n\t\thandler\n\t});\n\tconst tools: AgentTool[] = [\n\t\ttool('list_components', 'Lists components (id, name, variant values, instance count) and component sets with their variant options.', {}, () => controller.list(), {\n\t\t\treadOnly: true\n\t\t}),\n\t\ttool(\n\t\t\t'inspect_instance',\n\t\t\t'Describes an instance: its main component, variant, component property values (with types) and the overrides of its layers.',\n\t\t\t{ id: ID },\n\t\t\t({ id }) => {\n\t\t\t\tconst info = controller.inspect(id);\n\t\t\t\tif (!info) throw new Error('Not an instance (or a layer inside one).');\n\t\t\t\treturn info;\n\t\t\t},\n\t\t\t{ readOnly: true }\n\t\t),\n\t\ttool('create_component', 'Turns the given frame into a main component, or wraps the given layers in a new one.', { ids: IDS }, ({ ids }) =>\n\t\t\tcheck(controller.createComponent(ids))\n\t\t),\n\t\ttool('combine_as_variants', 'Wraps sibling components in a component set (variants named \"Property=Value, …\").', { ids: IDS }, ({ ids }) =>\n\t\t\tcheck(controller.combineAsVariants(ids))\n\t\t),\n\t\ttool(\n\t\t\t'create_instance',\n\t\t\t'Places an instance of a component or component set, optionally inside a frame, at a position, with property values.',\n\t\t\t{\n\t\t\t\tcomponentId: COMPONENT_ID,\n\t\t\t\tparentId: PARENT_ID,\n\t\t\t\tx: X,\n\t\t\t\ty: Y,\n\t\t\t\tproperties: { type: 'object', additionalProperties: true, description: 'Variant and component property values.' }\n\t\t\t},\n\t\t\t({ componentId, parentId, x, y, properties }) =>\n\t\t\t\tcheck(\n\t\t\t\t\tcontroller.createInstance(componentId, {\n\t\t\t\t\t\t...(parentId !== undefined ? { parentId } : {}),\n\t\t\t\t\t\t...(x !== undefined || y !== undefined ? { position: { x: x ?? 0, y: y ?? 0 } } : {}),\n\t\t\t\t\t\t...(properties ? { properties } : {})\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t{ required: ['componentId'] }\n\t\t),\n\t\ttool(\n\t\t\t'set_instance_properties',\n\t\t\t'Sets property values of instances: variant properties switch the variant (keeping compatible overrides), booleans toggle layers, text sets text, instance swap takes a component id.',\n\t\t\t{\n\t\t\t\tproperties: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\tdescription: 'Property name → value (see inspect_instance for the names and types).'\n\t\t\t\t},\n\t\t\t\tids: IDS\n\t\t\t},\n\t\t\t({ properties, ids }) => check(controller.setInstanceProperties(properties, ids)),\n\t\t\t{ required: ['properties'] }\n\t\t),\n\t\ttool(\n\t\t\t'swap_instance',\n\t\t\t'Makes instances show another component (or a set’s default variant), keeping overrides of layers with matching names.',\n\t\t\t{ componentId: COMPONENT_ID, ids: IDS },\n\t\t\t({ componentId, ids }) => check(controller.swapInstance(componentId, ids)),\n\t\t\t{ required: ['componentId'] }\n\t\t),\n\t\ttool(\n\t\t\t'reset_overrides',\n\t\t\t'Resets overrides: of a whole instance (default), of one layer, or one override group such as \"data.label\" or \"shape.fill\".',\n\t\t\t{ id: ID, group: { type: 'string', description: 'Override group, e.g. \"data.label\" or \"shape.fill\" (default: all).' } },\n\t\t\t({ id, group }) => check(controller.resetOverrides(id, group))\n\t\t),\n\t\ttool('push_overrides', 'Copies an instance’s overrides to its main component (and so to every other instance).', { id: ID }, ({ id }) =>\n\t\t\tcheck(controller.pushOverrides(id))\n\t\t),\n\t\ttool('detach_instance', 'Turns instances into plain frames that keep their current look.', { ids: IDS }, ({ ids }) => check(controller.detachInstance(ids))),\n\t\ttool('go_to_main_component', 'Selects the main component of an instance and returns its id.', { id: ID }, ({ id }) => {\n\t\t\tconst main = controller.goToMainComponent(id);\n\t\t\tif (!main) throw new Error('The main component was not found.');\n\t\t\treturn { id: main };\n\t\t}),\n\t\ttool('restore_main_component', 'Turns an instance whose main component was deleted back into the main component.', { id: ID }, ({ id }) =>\n\t\t\tcheck(controller.restoreMainComponent(id))\n\t\t)\n\t];\n\tif (hasVariantCommands(controller)) {\n\t\tconst SET = { type: 'string', description: 'Component set, or one of its variants (default: the selection’s set).' };\n\t\tconst VARIANT = { type: 'string', description: 'Variant id (default: the first selected variant).' };\n\t\tconst VALUES = { type: 'object', additionalProperties: { type: 'string' }, description: 'Variant property → value, e.g. { \"Size\": \"Medium\" }.' };\n\t\ttools.push(\n\t\t\ttool(\n\t\t\t\t'add_variant',\n\t\t\t\t'Adds a variant to a component set by duplicating one (default: the set’s default variant) with new property values. Unnamed properties keep the source’s values; new property names are added to every variant (\"Default\"). A combination another variant has is refused. Without values the next \"VariantN\" is used.',\n\t\t\t\t{ setId: SET, from: { type: 'string', description: 'Variant to duplicate.' }, variant: VALUES, x: X, y: Y },\n\t\t\t\t({ setId, from, variant, x, y }) =>\n\t\t\t\t\tcheck(\n\t\t\t\t\t\tcontroller.addVariant(setId, {\n\t\t\t\t\t\t\t...(from !== undefined ? { from } : {}),\n\t\t\t\t\t\t\t...(variant ? { variant } : {}),\n\t\t\t\t\t\t\t...(x !== undefined || y !== undefined ? { position: { x: x ?? 0, y: y ?? 0 } } : {})\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t),\n\t\t\ttool(\n\t\t\t\t'remove_variant',\n\t\t\t\t'Removes a variant from its component set (not the last one). Its instances switch to the closest remaining variant, keeping overrides.',\n\t\t\t\t{ id: VARIANT },\n\t\t\t\t({ id }) => check(controller.removeVariant(id))\n\t\t\t),\n\t\t\ttool(\n\t\t\t\t'set_variant_properties',\n\t\t\t\t'Sets a variant’s property values (merged with its current ones); new property names are added to every variant. A combination another variant has is refused.',\n\t\t\t\t{ values: VALUES, id: VARIANT },\n\t\t\t\t({ values, id }) => check(controller.setVariantProperties(values, id)),\n\t\t\t\t{ required: ['values'] }\n\t\t\t),\n\t\t\ttool(\n\t\t\t\t'rename_variant_property',\n\t\t\t\t'Renames a variant property across a component set (instances keep their variant).',\n\t\t\t\t{\n\t\t\t\t\tname: { type: 'string', description: 'Current property name.' },\n\t\t\t\t\tnewName: { type: 'string', description: 'New property name.' },\n\t\t\t\t\tsetId: SET\n\t\t\t\t},\n\t\t\t\t({ name, newName, setId }) => check(controller.renameVariantProperty(name, newName, setId)),\n\t\t\t\t{ required: ['name', 'newName'] }\n\t\t\t),\n\t\t\ttool(\n\t\t\t\t'rename_variant_value',\n\t\t\t\t'Renames a value of a variant property across a component set (instances keep their variant).',\n\t\t\t\t{\n\t\t\t\t\tproperty: { type: 'string', description: 'Variant property name.' },\n\t\t\t\t\tvalue: { type: 'string', description: 'Current value.' },\n\t\t\t\t\tnewValue: { type: 'string', description: 'New value.' },\n\t\t\t\t\tsetId: SET\n\t\t\t\t},\n\t\t\t\t({ property, value, newValue, setId }) => check(controller.renameVariantValue(property, value, newValue, setId)),\n\t\t\t\t{ required: ['property', 'value', 'newValue'] }\n\t\t\t)\n\t\t);\n\t}\n\treturn { id: options.id, namespace: options.namespace, tools };\n}\n"]}
|