pi-revit 0.4.0 → 0.5.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/AGENTS.md +167 -0
- package/CHANGELOG.md +465 -430
- package/README.md +604 -548
- package/bin/pi-revit.js +9 -9
- package/docs/architecture.md +271 -0
- package/docs/evaluation.md +434 -0
- package/docs/invariants.json +147 -0
- package/extensions/pi-revit/completion-monitor.ts +55 -0
- package/extensions/pi-revit/contracts.ts +146 -0
- package/extensions/pi-revit/discovery.ts +93 -0
- package/extensions/pi-revit/index.ts +342 -255
- package/extensions/pi-revit/instance-router.ts +86 -86
- package/extensions/pi-revit/platform-prompt.ts +40 -0
- package/extensions/pi-revit/scope-monitor.ts +114 -0
- package/extensions/pi-revit/script-library.ts +144 -144
- package/extensions/pi-revit/tool-catalog.ts +113 -14
- package/extensions/pi-revit/tool-documentation.ts +72 -0
- package/extensions/pi-revit/tool-schema.ts +8 -0
- package/package.json +8 -2
- package/scripts/build.ps1 +9 -9
- package/scripts/check-sdk.ps1 +66 -66
- package/scripts/check-tool-documentation.mjs +287 -0
- package/scripts/deploy.ps1 +16 -16
- package/scripts/generate-contracts.mjs +80 -0
- package/scripts/lib/platform.mjs +226 -0
- package/scripts/test-extension.mjs +15 -0
- package/skills/pi-revit/SKILL.md +30 -218
- package/skills/pi-revit/contracts.generated.json +3524 -0
- package/skills/pi-revit/references/execution-rules.md +41 -0
- package/skills/pi-revit/references/model-audit-export.md +38 -27
- package/skills/pi-revit/references/operation-recovery.md +33 -0
- package/skills/pi-revit/references/room-documentation.md +37 -26
- package/skills/pi-revit/references/tool-index.md +89 -0
- package/skills/pi-revit/references/tools/capture_view.md +62 -0
- package/skills/pi-revit/references/tools/change_element_types.md +65 -0
- package/skills/pi-revit/references/tools/create_tags.md +85 -0
- package/skills/pi-revit/references/tools/delete_elements.md +66 -0
- package/skills/pi-revit/references/tools/execute_csharp.md +81 -0
- package/skills/pi-revit/references/tools/export_documents.md +75 -0
- package/skills/pi-revit/references/tools/find_revit_tools.md +96 -0
- package/skills/pi-revit/references/tools/get_element_details.md +66 -0
- package/skills/pi-revit/references/tools/get_element_relationships.md +61 -0
- package/skills/pi-revit/references/tools/get_element_types.md +67 -0
- package/skills/pi-revit/references/tools/get_elements.md +87 -0
- package/skills/pi-revit/references/tools/get_linked_elements.md +79 -0
- package/skills/pi-revit/references/tools/get_linked_models.md +57 -0
- package/skills/pi-revit/references/tools/get_model_coordinates.md +64 -0
- package/skills/pi-revit/references/tools/get_model_health.md +53 -0
- package/skills/pi-revit/references/tools/get_model_overview.md +57 -0
- package/skills/pi-revit/references/tools/get_revit_operation.md +54 -0
- package/skills/pi-revit/references/tools/get_schedule_fields.md +62 -0
- package/skills/pi-revit/references/tools/get_schedules.md +71 -0
- package/skills/pi-revit/references/tools/manage_element_sets.md +92 -0
- package/skills/pi-revit/references/tools/manage_revit_instances.md +63 -0
- package/skills/pi-revit/references/tools/manage_revit_scripts.md +109 -0
- package/skills/pi-revit/references/tools/manage_schedules.md +90 -0
- package/skills/pi-revit/references/tools/manage_selection.md +66 -0
- package/skills/pi-revit/references/tools/manage_sheet_placements.md +82 -0
- package/skills/pi-revit/references/tools/manage_sheets.md +71 -0
- package/skills/pi-revit/references/tools/manage_views.md +95 -0
- package/skills/pi-revit/references/tools/measure_geometry.md +71 -0
- package/skills/pi-revit/references/tools/open_view.md +59 -0
- package/skills/pi-revit/references/tools/ping.md +41 -0
- package/skills/pi-revit/references/tools/query_spatial_elements.md +74 -0
- package/skills/pi-revit/references/tools/read_revit_result.md +53 -0
- package/skills/pi-revit/references/tools/search_api_docs.md +65 -0
- package/skills/pi-revit/references/tools/set_parameters.md +75 -0
- package/skills/pi-revit/references/tools/summarize_elements.md +64 -0
- package/skills/pi-revit/references/tools/transform_elements.md +79 -0
- package/skills/pi-revit/references/visual-verification.md +36 -0
- package/skills/pi-revit/tool-manifest.json +338 -0
- package/src/Revit/BridgeServer.cs +93 -87
- package/src/Revit/OperationStore.cs +178 -178
- package/src/Revit/ToolRegistry.cs +88 -57
- package/src/Revit/Tools/CaptureView.cs +10 -2
- package/src/Revit/Tools/ChangeElementTypes.cs +74 -60
- package/src/Revit/Tools/ChangeSet.cs +39 -0
- package/src/Revit/Tools/CreateTags.cs +107 -95
- package/src/Revit/Tools/DeleteElements.cs +53 -44
- package/src/Revit/Tools/DocumentGuard.cs +74 -64
- package/src/Revit/Tools/ElementNames.cs +103 -0
- package/src/Revit/Tools/ElementQueryScope.cs +27 -27
- package/src/Revit/Tools/ElementTraits.cs +53 -0
- package/src/Revit/Tools/ExecuteCsharp.cs +54 -45
- package/src/Revit/Tools/ExportDocuments.cs +129 -121
- package/src/Revit/Tools/GetElementDetails.cs +37 -41
- package/src/Revit/Tools/GetElementRelationships.cs +82 -76
- package/src/Revit/Tools/GetElementTypes.cs +8 -0
- package/src/Revit/Tools/GetElements.cs +75 -86
- package/src/Revit/Tools/GetLinkedElements.cs +89 -82
- package/src/Revit/Tools/GetLinkedModels.cs +73 -66
- package/src/Revit/Tools/GetModelCoordinates.cs +56 -49
- package/src/Revit/Tools/GetModelHealth.cs +7 -0
- package/src/Revit/Tools/GetModelOverview.cs +185 -158
- package/src/Revit/Tools/GetScheduleFields.cs +44 -37
- package/src/Revit/Tools/GetSchedules.cs +96 -89
- package/src/Revit/Tools/InheritedState.Summary.cs +57 -0
- package/src/Revit/Tools/InheritedState.cs +144 -0
- package/src/Revit/Tools/ManageElementSets.cs +114 -106
- package/src/Revit/Tools/ManageSchedules.cs +174 -164
- package/src/Revit/Tools/ManageSelection.cs +45 -37
- package/src/Revit/Tools/ManageSheetPlacements.cs +113 -97
- package/src/Revit/Tools/ManageSheets.cs +72 -63
- package/src/Revit/Tools/ManageViews.cs +115 -100
- package/src/Revit/Tools/MeasureGeometry.cs +60 -54
- package/src/Revit/Tools/ModelChanges.cs +154 -0
- package/src/Revit/Tools/ModelEditBatch.cs +105 -102
- package/src/Revit/Tools/ModelEditInputs.cs +49 -49
- package/src/Revit/Tools/OpenView.cs +9 -2
- package/src/Revit/Tools/ParameterResolver.cs +94 -0
- package/src/Revit/Tools/QuerySpatialElements.cs +70 -63
- package/src/Revit/Tools/SearchApiDocs.cs +72 -4
- package/src/Revit/Tools/SetParameters.cs +60 -79
- package/src/Revit/Tools/SpatialBounds.cs +30 -30
- package/src/Revit/Tools/SummarizeElements.cs +94 -87
- package/src/Revit/Tools/ToolContract.cs +48 -0
- package/src/Revit/Tools/ToolSupport.cs +5 -1
- package/src/Revit/Tools/TransformElements.cs +72 -57
- package/workspace/AGENTS.md +26 -20
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Shared offline platform helpers for the contract generator and the documentation
|
|
2
|
+
// checker. Nothing here contacts Revit, the network or a live model: bridge metadata
|
|
3
|
+
// comes from the real C# registry compiled with stubbed tool bodies, and the Pi
|
|
4
|
+
// extension is registered against an isolated fake bridge.
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
export const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
14
|
+
export const skillRoot = path.join(root, 'skills', 'pi-revit');
|
|
15
|
+
export const manualRoot = path.join(skillRoot, 'references', 'tools');
|
|
16
|
+
export const snapshotPath = path.join(skillRoot, 'contracts.generated.json');
|
|
17
|
+
export const manifestPath = path.join(skillRoot, 'tool-manifest.json');
|
|
18
|
+
export const toolIndexPath = path.join(skillRoot, 'references', 'tool-index.md');
|
|
19
|
+
|
|
20
|
+
export function resolvePiDependencies() {
|
|
21
|
+
const candidates = [process.env.PI_CODING_AGENT_PATH,
|
|
22
|
+
path.join(path.dirname(process.execPath), 'node_modules', '@earendil-works', 'pi-coding-agent')].filter(Boolean);
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
const require = createRequire(path.join(path.resolve(candidate), 'package.json'));
|
|
25
|
+
try { require.resolve('jiti'); require.resolve('typebox/value'); return require; } catch {}
|
|
26
|
+
}
|
|
27
|
+
throw new Error('Set PI_CODING_AGENT_PATH to an existing @earendil-works/pi-coding-agent directory containing jiti and typebox. These checks never install dependencies.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createLoader(piRequire) {
|
|
31
|
+
const { createJiti } = piRequire('jiti');
|
|
32
|
+
return createJiti(import.meta.url, { alias: { typebox: piRequire.resolve('typebox') }, moduleCache: false });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Real ToolRegistry.DescribeAll() output, via the Roslyn schema extractor. */
|
|
36
|
+
export async function extractBridgeDescriptors() {
|
|
37
|
+
const scratch = await mkdtemp(path.join(os.tmpdir(), 'pi-revit-contracts-'));
|
|
38
|
+
try {
|
|
39
|
+
const output = path.join(scratch, 'bridge.json');
|
|
40
|
+
const run = spawnSync('dotnet', ['run', '--project', path.join(root, 'tests', 'tool-documentation', 'schema-extractor.csproj'), '--', root, output],
|
|
41
|
+
{ cwd: root, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, env: { ...process.env, DOTNET_CLI_TELEMETRY_OPTOUT: '1', DOTNET_SKIP_FIRST_TIME_EXPERIENCE: '1' } });
|
|
42
|
+
if (run.error || run.status !== 0)
|
|
43
|
+
throw new Error(`Bridge schema extraction failed (installed .NET SDK 8+ required).\n${run.error?.message ?? ''}\n${run.stdout}\n${run.stderr}`);
|
|
44
|
+
return JSON.parse(await readFile(output, 'utf8'));
|
|
45
|
+
} finally { await rm(scratch, { recursive: true, force: true }); }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Register the real extension against an in-memory bridge that serves `descriptors`.
|
|
50
|
+
* Returns the registrations and event handlers. Every non-metadata route and all
|
|
51
|
+
* real network access are rejected, and no registered execute() is invoked.
|
|
52
|
+
*/
|
|
53
|
+
export async function registerExtension(loader, descriptors, addinVersion) {
|
|
54
|
+
const scratch = await mkdtemp(path.join(os.tmpdir(), 'pi-revit-register-'));
|
|
55
|
+
const original = { fetch: globalThis.fetch, appdata: process.env.APPDATA, setInterval: globalThis.setInterval };
|
|
56
|
+
process.env.APPDATA = scratch;
|
|
57
|
+
await mkdir(path.join(scratch, 'RevitBridge'));
|
|
58
|
+
await writeFile(path.join(scratch, 'RevitBridge', 'bridge.json'), JSON.stringify({ baseUrl: 'http://platform-fixture.invalid', token: 'offline-fixture' }));
|
|
59
|
+
const routes = [];
|
|
60
|
+
globalThis.fetch = async (input, init) => {
|
|
61
|
+
const url = new URL(typeof input === 'string' || input instanceof URL ? input : input.url);
|
|
62
|
+
if (url.origin !== 'http://platform-fixture.invalid') throw new Error('real network access is forbidden');
|
|
63
|
+
if ((init?.method ?? 'GET') !== 'GET' || !['/ping', '/tools'].includes(url.pathname)) throw new Error(`tool execution is forbidden: ${url.pathname}`);
|
|
64
|
+
routes.push(url.pathname);
|
|
65
|
+
return new Response(JSON.stringify(url.pathname === '/tools' ? { tools: descriptors } : { ok: true, addinVersion }));
|
|
66
|
+
};
|
|
67
|
+
globalThis.setInterval = () => { throw new Error('Background polling is forbidden in platform fixtures'); };
|
|
68
|
+
const registered = new Map(), events = new Map();
|
|
69
|
+
let active = [];
|
|
70
|
+
const pi = {
|
|
71
|
+
registerTool(tool) { if (registered.has(tool.name)) throw new Error(`duplicate registered tool ${tool.name}`); registered.set(tool.name, tool); active = [...active, tool.name]; },
|
|
72
|
+
on(name, handler) { events.set(name, handler); },
|
|
73
|
+
getActiveTools() { return [...active]; },
|
|
74
|
+
setActiveTools(names) { active = [...names]; },
|
|
75
|
+
sendMessage() { throw new Error('Messages are forbidden in platform fixtures'); },
|
|
76
|
+
};
|
|
77
|
+
const restore = async () => {
|
|
78
|
+
await events.get('session_shutdown')?.({}, {});
|
|
79
|
+
globalThis.fetch = original.fetch; globalThis.setInterval = original.setInterval;
|
|
80
|
+
if (original.appdata === undefined) delete process.env.APPDATA; else process.env.APPDATA = original.appdata;
|
|
81
|
+
await rm(scratch, { recursive: true, force: true });
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
const { default: connector } = await loader.import(path.join(root, 'extensions', 'pi-revit', 'index.ts'));
|
|
85
|
+
await connector(pi);
|
|
86
|
+
} catch (error) { await restore(); throw error; }
|
|
87
|
+
return { pi, registered, events, routes, activeTools: () => [...active], restore };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Human-readable type, requiredness, default and allowed values of one JSON schema property. */
|
|
91
|
+
function describeProperty(schema) {
|
|
92
|
+
const values = [];
|
|
93
|
+
let type = schema?.type;
|
|
94
|
+
if (Array.isArray(schema?.enum)) values.push(...schema.enum);
|
|
95
|
+
for (const variant of schema?.anyOf ?? schema?.oneOf ?? []) {
|
|
96
|
+
if (variant.const !== undefined) { values.push(variant.const); type ??= typeof variant.const; }
|
|
97
|
+
else if (variant.type) type = type ? `${type} | ${variant.type}` : variant.type;
|
|
98
|
+
}
|
|
99
|
+
if (schema?.const !== undefined) values.push(schema.const);
|
|
100
|
+
if (type === 'array') {
|
|
101
|
+
const items = schema.items ?? {};
|
|
102
|
+
const itemValues = Array.isArray(items.enum) ? items.enum : (items.anyOf ?? []).filter(v => v.const !== undefined).map(v => v.const);
|
|
103
|
+
values.push(...itemValues);
|
|
104
|
+
type = `array of ${items.type ?? (itemValues.length ? typeof itemValues[0] : 'value')}`;
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(type)) type = type.join(' | ');
|
|
107
|
+
return { type: type ?? 'value', values, default: schema?.default };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const VERIFICATION_TEXT = {
|
|
111
|
+
reread: 'query the changed state again with a read tool',
|
|
112
|
+
capture: 'capture the visible result and inspect the image',
|
|
113
|
+
inspect_output: 'open and inspect the produced file',
|
|
114
|
+
none: 'no durable outcome to check; report what was done',
|
|
115
|
+
};
|
|
116
|
+
const cell = text => String(text).replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
117
|
+
|
|
118
|
+
export function alternativeText(limit) {
|
|
119
|
+
const ref = limit.alternative.ref ? cell(limit.alternative.ref) : '';
|
|
120
|
+
switch (limit.alternative.kind) {
|
|
121
|
+
case 'tool': return `Tool: ${ref}`;
|
|
122
|
+
case 'api': return `Revit API: ${ref}. ${limit.lookup ? `Check all its members in one call: \`search_api_docs\` with query \`${cell(limit.lookup.query)}\`` : 'Check with `search_api_docs`'}, then use \`execute_csharp\` within the requested scope.`;
|
|
123
|
+
case 'user': return `User action: ${ref}`;
|
|
124
|
+
case 'revit_unsupported': return `Not offered by the Revit API (${ref}). Report this with the evidence checked.`;
|
|
125
|
+
default: return ref;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const CONTRACT_START = '<!-- generated:contract:start (npm run generate:contracts; do not edit this block) -->';
|
|
130
|
+
export const CONTRACT_END = '<!-- generated:contract:end -->';
|
|
131
|
+
|
|
132
|
+
export function renderManualBlock(tool) {
|
|
133
|
+
const lines = [CONTRACT_START, '## Contract (generated)', ''];
|
|
134
|
+
const tier = tool.source === 'native' ? 'Pi extension utility, always active'
|
|
135
|
+
: tool.tier === 'advanced' ? 'bridge tool, advanced tier: activate it with `find_revit_tools`' : 'bridge tool, core tier: active by default';
|
|
136
|
+
lines.push(`- **Source:** ${tier}.`);
|
|
137
|
+
lines.push(`- **Writes model:** ${tool.write ? 'yes' : 'no'}. **Effects:** ${tool.effects.length ? tool.effects.join(', ') : 'none'}. **Requires an open document:** ${tool.requires_document ? 'yes' : 'no'}.`);
|
|
138
|
+
if (tool.document_kinds?.length && tool.requires_document)
|
|
139
|
+
lines.push(`- **Works in:** ${tool.document_kinds.length > 1 ? 'project and family documents' : `${tool.document_kinds[0]} documents only; the bridge refuses other documents before running, with the route to use instead`}.`);
|
|
140
|
+
if (tool.verification) lines.push(`- **Verify the outcome:** \`${tool.verification}\`: ${VERIFICATION_TEXT[tool.verification]}.`);
|
|
141
|
+
lines.push(`- **Contract hash:** \`${tool.contract_hash}\`. \`find_revit_tools\` compares it with the selected bridge's live contract.`);
|
|
142
|
+
lines.push('', '| Input | Type | Required | Default | Allowed values |', '| --- | --- | --- | --- | --- |');
|
|
143
|
+
const schema = tool.parameters ?? {};
|
|
144
|
+
const required = new Set(schema.required ?? []);
|
|
145
|
+
const properties = Object.entries(schema.properties ?? {});
|
|
146
|
+
if (!properties.length) lines.push('| _(none)_ | | | | |');
|
|
147
|
+
for (const [name, property] of properties) {
|
|
148
|
+
const d = describeProperty(property);
|
|
149
|
+
lines.push(`| \`${name}\` | ${cell(d.type)} | ${required.has(name) ? 'yes' : 'no'} | ${d.default === undefined ? '' : `\`${cell(JSON.stringify(d.default))}\``} | ${d.values.map(v => `\`${cell(v)}\``).join(', ')} |`);
|
|
150
|
+
}
|
|
151
|
+
if (tool.source === 'bridge') lines.push('', 'Bridge calls also accept `_operation_id`, only to retry an identical earlier request (see [operation recovery](../operation-recovery.md)).');
|
|
152
|
+
if (tool.limits.length) {
|
|
153
|
+
lines.push('', '| Not covered by this tool | Use instead |', '| --- | --- |');
|
|
154
|
+
for (const limit of tool.limits) lines.push(`| ${cell(limit.what)} | ${alternativeText(limit)} |`);
|
|
155
|
+
}
|
|
156
|
+
lines.push(CONTRACT_END);
|
|
157
|
+
return lines.join('\n');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Replace the generated block, or insert it before the first "## Inputs" heading (else the second H2). */
|
|
161
|
+
export function applyManualBlock(markdown, block) {
|
|
162
|
+
const eol = markdown.includes('\r\n') ? '\r\n' : '\n';
|
|
163
|
+
const body = block.replace(/\n/g, eol);
|
|
164
|
+
const start = markdown.indexOf(CONTRACT_START), end = markdown.indexOf(CONTRACT_END);
|
|
165
|
+
if (start >= 0 && end > start) return markdown.slice(0, start) + body + markdown.slice(end + CONTRACT_END.length);
|
|
166
|
+
const headings = [...markdown.matchAll(/^## .*$/gm)];
|
|
167
|
+
const anchor = headings.find(h => /^## Inputs/.test(h[0])) ?? headings[1];
|
|
168
|
+
if (!anchor) return markdown.trimEnd() + eol + eol + body + eol;
|
|
169
|
+
return markdown.slice(0, anchor.index) + body + eol + eol + markdown.slice(anchor.index);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const INDEX_START = '<!-- generated:tool-index:start (npm run generate:contracts; do not edit this block) -->';
|
|
173
|
+
export const INDEX_END = '<!-- generated:tool-index:end -->';
|
|
174
|
+
|
|
175
|
+
export function renderToolIndex(manifest, contracts) {
|
|
176
|
+
const byName = new Map(contracts.map(tool => [tool.name, tool]));
|
|
177
|
+
const lines = [INDEX_START];
|
|
178
|
+
for (const group of manifest.groups) {
|
|
179
|
+
lines.push('', `## ${group.title}`, '', '| Manual | Use | Tier | Verify |', '| --- | --- | --- | --- |');
|
|
180
|
+
for (const tool of manifest.tools.filter(entry => entry.group === group.id)) {
|
|
181
|
+
const contract = byName.get(tool.name);
|
|
182
|
+
const tier = tool.source === 'native' ? 'native' : contract?.tier ?? '';
|
|
183
|
+
lines.push(`| [${tool.name}](tools/${tool.name}.md) | ${cell(tool.summary)} | ${tier} | ${contract?.verification ?? ''} |`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
lines.push('', '## Workflows and shared guidance', '', '| Guide | Kind | Use |', '| --- | --- | --- |');
|
|
187
|
+
for (const guide of manifest.guidance) lines.push(`| [${guide.name}](${guide.path.replace(/^references\//, '')}) | ${guide.kind} | ${cell(guide.summary)} |`);
|
|
188
|
+
lines.push(INDEX_END);
|
|
189
|
+
return lines.join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function applyIndexBlock(markdown, block) {
|
|
193
|
+
const eol = markdown.includes('\r\n') ? '\r\n' : '\n';
|
|
194
|
+
const body = block.replace(/\n/g, eol);
|
|
195
|
+
const start = markdown.indexOf(INDEX_START), end = markdown.indexOf(INDEX_END);
|
|
196
|
+
if (start < 0 || end < start) throw new Error(`tool-index.md needs ${INDEX_START} ... ${INDEX_END} markers`);
|
|
197
|
+
return markdown.slice(0, start) + body + markdown.slice(end + INDEX_END.length);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Score the discovery corpus against packaged documentation (no bridge needed).
|
|
202
|
+
* An entry passes when its expected tool or guidance appears in the top_n results,
|
|
203
|
+
* or, for expect_fallback, when the response carries the capability route.
|
|
204
|
+
*/
|
|
205
|
+
export async function evaluateCorpus(loader, corpus, addinVersion) {
|
|
206
|
+
const fixture = await registerExtension(loader, [], addinVersion);
|
|
207
|
+
try {
|
|
208
|
+
const find = fixture.registered.get('find_revit_tools');
|
|
209
|
+
const results = [];
|
|
210
|
+
for (const entry of corpus.entries) {
|
|
211
|
+
const response = (await find.execute('corpus', { scope: 'documentation', query: entry.q, limit: corpus.top_n })).details;
|
|
212
|
+
const tools = response.tools.map(tool => tool.name), guides = response.guidance.map(guide => guide.name);
|
|
213
|
+
const pass = entry.expect_fallback ? Boolean(response.fallback) && response.match === 'none'
|
|
214
|
+
: entry.expect_guidance ? guides.slice(0, corpus.top_n).includes(entry.expect_guidance)
|
|
215
|
+
: tools.slice(0, corpus.top_n).includes(entry.expect);
|
|
216
|
+
results.push({ ...entry, pass, match: response.match, got: entry.expect_guidance ? guides : tools });
|
|
217
|
+
}
|
|
218
|
+
return results;
|
|
219
|
+
} finally { await fixture.restore(); }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Candidate RevitAPI.xml files for optional API-reference verification. */
|
|
223
|
+
export function revitApiXmlPaths() {
|
|
224
|
+
const roots = [process.env.REVIT_API_PATH, 'C:\\Program Files\\Autodesk\\Revit 2025', 'C:\\Program Files\\Autodesk\\Revit 2026', 'C:\\Program Files\\Autodesk\\Revit 2027'].filter(Boolean);
|
|
225
|
+
return roots.map(directory => path.join(directory, 'RevitAPI.xml')).filter(file => existsSync(file));
|
|
226
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
// Run sequentially: fixtures replace fetch, timers and discovery paths in-process.
|
|
6
|
+
// Each module has a separate process and uses Node's test API directly.
|
|
7
|
+
const directory = new URL('../tests/result-contract/', import.meta.url);
|
|
8
|
+
for (const file of readdirSync(directory).filter(name => name.endsWith('.test.mjs')).sort()) {
|
|
9
|
+
process.stdout.write(`\n${file}\n`);
|
|
10
|
+
const result = spawnSync(process.execPath, [fileURLToPath(new URL(file, directory))], {
|
|
11
|
+
stdio: 'inherit', env: process.env,
|
|
12
|
+
});
|
|
13
|
+
if (result.error) throw result.error;
|
|
14
|
+
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
15
|
+
}
|
package/skills/pi-revit/SKILL.md
CHANGED
|
@@ -1,227 +1,39 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pi-revit
|
|
3
|
-
description:
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Revit
|
|
7
|
-
|
|
8
|
-
Work with the live Revit model. The bridge targets Revit 2025, 2026, and 2027; the 0.3.0 changes were tested live on Revit 2025. Bridge document tools require Revit running with a project open; `ping` and `search_api_docs` work without a document. `read_revit_result` reads an already saved result locally without contacting Revit. `get_revit_operation` contacts the running bridge without needing an open document or waiting for the model thread.
|
|
9
|
-
|
|
10
|
-
## Tool selection
|
|
11
|
-
|
|
12
|
-
| Task | Tool |
|
|
13
|
-
|------|------|
|
|
14
|
-
| Bridge alive? Which Revit version? | `ping` |
|
|
15
|
-
| List local Revit sessions or choose the intended instance | `manage_revit_instances` (native extension tool) |
|
|
16
|
-
| Find and activate specialist tools | `find_revit_tools` (local catalogue) |
|
|
17
|
-
| Orientation: project info, units, levels, grids, category counts | `get_model_overview` |
|
|
18
|
-
| Read base points, site/project locations, or map internal points to active shared coordinates | `get_model_coordinates` (advanced) |
|
|
19
|
-
| List or count elements of ANY category (walls, doors, rooms, sheets, views, ...) | `get_elements` |
|
|
20
|
-
| Count a whole query scope by category, type, level, or parameter value | `summarize_elements` (advanced) |
|
|
21
|
-
| Retain and reread a temporary snapshot of matching host elements | `manage_element_sets` (advanced) |
|
|
22
|
-
| Read parameter VALUES, location, bounding box, materials of specific elements | `get_element_details` |
|
|
23
|
-
| List element types / family symbols; "used vs merely loaded" | `get_element_types` |
|
|
24
|
-
| List placed Revit links, load status, document identities, placement transforms | `get_linked_models` (advanced) |
|
|
25
|
-
| Query or count elements inside one loaded Revit link | `get_linked_elements` (advanced) |
|
|
26
|
-
| Find host elements whose bounding boxes intersect or fit inside a region | `query_spatial_elements` (advanced) |
|
|
27
|
-
| Measure explicit points or approximate separation of element bounding boxes | `measure_geometry` (advanced) |
|
|
28
|
-
| List schedules or read their fields and displayed cells | `get_schedules` (advanced) |
|
|
29
|
-
| Discover fields eligible for an existing schedule | `get_schedule_fields` (advanced) |
|
|
30
|
-
| Create or configure a regular schedule | `manage_schedules` (advanced) |
|
|
31
|
-
| Create element, room, space, or area tags in one view | `create_tags` (advanced) |
|
|
32
|
-
| Inspect an element's host, type, members, joined geometry, or dependents | `get_element_relationships` (advanced) |
|
|
33
|
-
| Read or change the user's selection; zoom; temporary isolate | `manage_selection` |
|
|
34
|
-
| Put a view or sheet on the user's screen (activate it) | `open_view` |
|
|
35
|
-
| Write or preview parameter values and renames; optional atomic batch | `set_parameters` |
|
|
36
|
-
| Move, copy, or rotate a selection together | `transform_elements` (advanced) |
|
|
37
|
-
| Preview or perform deletion, including Revit's reported deletion cascade | `delete_elements` (advanced) |
|
|
38
|
-
| Change element types with per-target results | `change_element_types` (advanced) |
|
|
39
|
-
| Create plans, isometric 3D views, or sections; duplicate or update views | `manage_views` (advanced) |
|
|
40
|
-
| Create, rename, or renumber drawing sheets | `manage_sheets` (advanced) |
|
|
41
|
-
| List, place, or move views and schedules on sheets | `manage_sheet_placements` (advanced) |
|
|
42
|
-
| Look up Revit API classes/members/signatures | `search_api_docs` |
|
|
43
|
-
| Other model tasks (create model elements, custom annotation, ...) | `execute_csharp` |
|
|
44
|
-
| Save, inspect, and run an exact reusable script version; inspect run history | `manage_revit_scripts` (native extension tool) |
|
|
45
|
-
| PNG snapshot of a view (visual QA) | `capture_view` (advanced) |
|
|
46
|
-
| PDF/DWG/PNG/IFC file export | `export_documents` (advanced) |
|
|
47
|
-
| Warnings / model quality audit | `get_model_health` (advanced) |
|
|
48
|
-
| Continue a saved large tool result | `read_revit_result` (local, no Revit call) |
|
|
49
|
-
| Check progress or recover a timed-out call's retained result | `get_revit_operation` (native extension tool, local bridge) |
|
|
50
|
-
|
|
51
|
-
## Recipes
|
|
1
|
+
---
|
|
2
|
+
name: pi-revit
|
|
3
|
+
description: Inspect, edit, document, script, and export Autodesk Revit models using PI-Revit. Use for questions about the connected project, selecting Revit capabilities, or carrying out model and drawing workflows. Routes to focused tool manuals and execution guidance.
|
|
4
|
+
---
|
|
52
5
|
|
|
53
|
-
|
|
6
|
+
# PI-Revit
|
|
54
7
|
|
|
55
|
-
|
|
56
|
-
- [Model audit and export](references/model-audit-export.md): counts, health checks, reusable sets, parameter review, and traceable model exports.
|
|
8
|
+
Choose the path that matches the user's request. This skill routes tasks; the always-present PI-Revit protocol states the capability, completion, evidence, identity and language rules for every tool. Executable schemas and bridge checks remain authoritative. Guidance does not grant permission for additional edits, exports, model saving, or deployment.
|
|
57
9
|
|
|
58
|
-
|
|
10
|
+
## Choose the task path
|
|
59
11
|
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
- The ID belongs to one open document in one bridge session. Refresh it after closing/reopening the model or restarting Revit. If the guard rejects a call, activate the intended model and obtain its overview again; do not blindly substitute the currently active model's ID.
|
|
64
|
-
- `get_elements` is the listing/counting primitive (`count_only: true` for bare counts). It returns identity fields (id, name, category, typeName, levelId), with optional `parameter_names` projections for up to 20 parameter identities. Use `get_element_details` for full inspection. Prefer a `category` or `of_class` scope when filtering by a parameter's display name.
|
|
65
|
-
- The selection pipeline is `get_elements` -> ids -> `manage_selection` (action `set`); there is no inline filter on selection.
|
|
66
|
-
- `set_parameters` handles bulk parameter writes and renames (the `Name` parameter covers levels, views, sheets, types). Default batches can commit partial successes; `atomic: true` rolls the whole batch back if any update fails. Use `preview: true` to validate a proposed batch and roll back its model changes. Inspect `committed`, `succeeded`, `proposed`, `failed`, `commitWarnings`, and `commit_validation_performed` before describing the outcome.
|
|
67
|
-
- Parameter display names are LOCALIZED: in a non-English Revit UI, `Mark`, `Comments`, and every other display name appear under their translated names. When a display-name lookup or `parameter_names` filter finds nothing, or the document may be non-English, use the language-independent `BuiltInParameter` enum name instead (e.g. `ALL_MODEL_MARK` for Mark, `ALL_MODEL_INSTANCE_COMMENTS` for Comments) — `set_parameters`, `get_element_details.parameter_names`, and `get_elements` filter rules all accept them, and `get_element_details` reports each parameter's `builtInParameter` name for discovery.
|
|
68
|
-
- Before writing `execute_csharp` code, verify unfamiliar classes/members with `search_api_docs` (works with no document open; first query builds the index and takes a few seconds). The top match carries its remarks, parameter docs, and returns inline, and every public API enum value is searchable — trust the result over guessing or web search; narrow the query to promote a different match into the top slot.
|
|
69
|
-
- `export_documents` defaults to `Documents\pi-revit\Models\<model title>--<identity hash>\exports`. Use its returned `outputDir` and file paths as authoritative; do not construct a destination from the title or opaque `project.documentId`. Saved paths and cloud/server identities determine the folder; unsaved/unavailable identities use a session fallback. Save As can select a new folder. Pass `output_dir` when the user requests a different destination. Existing title-only folders remain untouched.
|
|
70
|
-
- Large tool payloads return a `result_id`, `file_path`, and continuation instructions. Call `read_revit_result` with that ID and `offset: 0`, then follow `next_offset` until `has_more` is false. Concatenate `text` fragments in order; each fragment is not a standalone JSON result. Offsets count UTF-16 code units. IDs last for the current extension instance; after reload, use the returned absolute file path with `read` while the file exists. Retrieval does not replace the original query's pagination or remove its limits.
|
|
12
|
+
- **Explain or plan:** answer from relevant Revit knowledge and tool manuals. A general Revit explanation does not require an open model. `find_revit_tools` with `scope: "documentation"` can find packaged manuals without connecting to Revit. Connect only when the answer depends on the actual project. Do not turn a question into a modification.
|
|
13
|
+
- **Inspect:** select the intended session and read the requested model scope. Use `get_elements` for listings or `count_only: true` for a simple count. An audit does not authorize repairs, selection changes, or exports.
|
|
14
|
+
- **Modify or produce a deliverable:** establish model identity and scope, inspect the existing state, read the relevant manuals, then execute and verify requested work. Use preview and atomic behavior where they help validate a proposed change. A successful transaction does not save the Revit file.
|
|
71
15
|
|
|
72
|
-
##
|
|
16
|
+
## Essential rules
|
|
73
17
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
18
|
+
1. Before working against a model, read [execution rules](references/execution-rules.md). For several Revit sessions or a stale selection, use [manage_revit_instances](references/tools/manage_revit_instances.md). Select the intended model; do not substitute whichever document happens to be active.
|
|
19
|
+
2. Obtain `project.documentId` from `get_model_overview` before edits and pass it unchanged as `expected_document_id`, including previews. The same guard applies to export, view activation, selection changes, and every `manage_sheet_placements` action. Refresh after reopening the document, restarting Revit, or changing instances. A title is insufficient.
|
|
20
|
+
3. Discover dedicated capabilities with [find_revit_tools](references/tools/find_revit_tools.md) before custom code; search with English task words. Specialist tools begin inactive. Registration, activation, and reading a manual are separate actions: read the relevant returned manual, and inspect the activated public schema before calling the tool. Use the [tool index](references/tool-index.md) for all public tools and direct manual links; do not load every manual.
|
|
21
|
+
4. Each tool declares what it does not cover and what to use instead. When a tool or manual does not cover the request, follow that alternative. When nothing dedicated fits, verify the API members with [search_api_docs](references/tools/search_api_docs.md) and use [execute_csharp](references/tools/execute_csharp.md) within the requested scope. Report an operation as not possible only after that check, naming what you checked and whether the gap is a missing tool, the Revit API, or a user decision. <!-- inv:capability-claims-checked -->
|
|
22
|
+
5. Discover IDs and parameter identities from the model. Display names are localized; prefer discovered `BuiltInParameter` names or shared GUIDs where supported. Respect each tool's units and coordinate frame; raw measurements commonly use internal feet.
|
|
23
|
+
6. Check `committed`, `succeeded`, `proposed`, `failed`, warnings, and commit-validation status. Rolled-back proposals are not retained changes. Discard preview-created or replacement IDs after rollback; reuse only committed IDs.
|
|
24
|
+
7. For large responses, use [read_revit_result](references/tools/read_revit_result.md) and follow every continuation needed for the requested result. Saved-result fragments and the original tool's query pages are separate layers.
|
|
25
|
+
8. For a timeout, cancellation, uncertain outcome, or incomplete result, read [operation recovery](references/operation-recovery.md) before repeating an action. Check the original receipt. Only an identical request with its original `_operation_id` is a deduplicated retry.
|
|
26
|
+
9. For visible changes or graphical deliverables, read [visual verification](references/visual-verification.md), capture the actual result, and inspect the image. Correct only what breaks the request; once its requirements are verified, stop and report, offering further improvements as suggestions. Report blocked verification honestly. Evidence capture does not authorize saving the model.
|
|
79
27
|
|
|
80
|
-
##
|
|
28
|
+
## Read the detail needed for this task
|
|
81
29
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
30
|
+
| Need | Read |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Choose among tools; inspect input/output and effects | [Tool index and individual manuals](references/tool-index.md) |
|
|
33
|
+
| Room plans/sections, tags, schedules, sheets | [Room documentation workflow](references/room-documentation.md) |
|
|
34
|
+
| Multi-step model audit, data review, and requested exports | [Model audit and export workflow](references/model-audit-export.md) |
|
|
35
|
+
| No dedicated tool covers the operation, or an unfamiliar Revit API member | The tool's declared alternative; else [search_api_docs](references/tools/search_api_docs.md), then [execute_csharp](references/tools/execute_csharp.md) |
|
|
36
|
+
| Save, inspect, or run a reusable script | [manage_revit_scripts](references/tools/manage_revit_scripts.md) |
|
|
37
|
+
| Bridge unavailable or version mismatch | [ping](references/tools/ping.md) and [operation recovery](references/operation-recovery.md) |
|
|
86
38
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- `get_model_coordinates` requires a project document and explicit length `unit`. It returns project/survey base points, active project location, site data, and project-location pages (`offset`/`limit`, default 50, maximum 100). Optional `points` contains up to 100 `[x,y,z]` positions in document internal axes. The active location's `GetProjectPosition` maps them to shared east/west, north/south, and elevation values. All returned lengths use the requested unit; angles and latitude/longitude use degrees. Paging applies to locations, not input points.
|
|
90
|
-
- This is Revit's active shared-coordinate mapping. Do not infer a GIS coordinate reference system, datum, or projected map units from site latitude/longitude or base-point values. The tool does not acquire, publish, or modify coordinates.
|
|
91
|
-
|
|
92
|
-
## Spatial queries and measurements
|
|
93
|
-
|
|
94
|
-
- `query_spatial_elements` reads host elements using model axis-aligned bounding boxes. Supply region `min`/`max` vectors and explicit length `unit` in document internal axes. `relation: "intersects"` is the default; `"inside"` requires the entire element box to fit inside the region. Both include touching boundaries. Linked contents are not traversed.
|
|
95
|
-
- Its `query` accepts the whole-scope element filters, with no query-level paging or projections. Narrow it to at most 10,000 candidates before spatial testing; the cap applies even when few elements would match the region. Results sort by element ID and use outer `offset`/`limit` (default 100, maximum 200). Follow `next_offset` and inspect `candidate_count`, `without_bounds_count`, and warnings. Elements without a model box are counted separately and omitted from matches.
|
|
96
|
-
- `measure_geometry` with `mode: "point_distance"` requires explicit `point_a` and `point_b`. It returns exact Euclidean distance between those supplied points and signed `delta` from A to B, not a distance between element surfaces. `mode: "bounding_box_gap"` instead requires `element_a_id` and `element_b_id`, both in the host; missing boxes fail the call. Do not mix point inputs with element IDs or pass `clearance` in point mode.
|
|
97
|
-
- Both spatial tools require `unit` from `millimeters`, `centimeters`, `meters`, `feet`, or `inches`; it applies to all coordinate/distance inputs and outputs. Measurement's optional nonnegative `clearance` is only a box-proximity threshold. `box_gap_below_clearance` uses strict less-than, while `boxes_overlap_or_touch` includes contact.
|
|
98
|
-
- Bounding boxes may include nonphysical geometry. Their gap is only a lower bound on geometry separation; a zero gap or a threshold hit is a candidate for review, not a confirmed clash or physical clearance failure. Preserve the returned `method` and `approximate` labels in reports and do not turn box matches into verified collision claims.
|
|
99
|
-
|
|
100
|
-
## Parameter projections, summaries, and element sets
|
|
101
|
-
|
|
102
|
-
- `get_elements.parameter_names` accepts display names, built-in parameter names, or `guid:<GUID>`. Each projection reports `requested`, `isType`, `found`, `ambiguous`, and every matching parameter in `matches`. Missing and duplicate display-name matches are explicit; do not silently select one ambiguous match. `include_type_parameters: true` adds the same requested parameters from the type, where one exists. `count_only` returns no projections.
|
|
103
|
-
- Parameter `value` is the raw Revit value; measurable numeric values use internal units. `displayValue` is separately formatted for the document. This differs from numeric query filter inputs and `set_parameters` inputs, which use the supplied `unit` or the document's display units.
|
|
104
|
-
- `summarize_elements` and `manage_element_sets` creation accept a `query` containing category, class, level, type, active-view, and parameter-filter scope. The query always covers all matches: query-level paging, `count_only`, fields, and projections are rejected. Both tools require at most 10,000 matches; narrow larger scopes. An omitted query means all host instances, subject to this cap.
|
|
105
|
-
- `summarize_elements` groups by `category`, `typeName`, `levelId`, or `parameter`. Parameter grouping requires `parameter`; `type_parameter: true` uses the type instead of the instance. Groups use exact raw values, including internal numeric units, and distinguish a missing parameter from a present parameter with a null value. Ambiguous parameter names are rejected. The outer `offset`/`limit` pages groups only (default 100, maximum 500); `total_elements` covers the whole matching scope.
|
|
106
|
-
- `manage_element_sets` actions are `create`, `list`, `read`, and `forget`. Sets retain membership and element identities without changing the model or selection. Each expires 30 minutes after creation; reads do not extend its lifetime. At most 32 sets are retained across the bridge session. A set belongs to the exact open document and does not survive closing/reopening that document or restarting the bridge. `list` shows only sets for the active document.
|
|
107
|
-
- Read a set with its `set_id`, using `offset`/`limit` (default 100, maximum 1000). Membership stays fixed; filters are not rerun, but returned values are current. Optional `parameter_names` and `include_type_parameters` use the same projection contract as `get_elements`. Check `missing` for deleted or identity-changed members before passing returned IDs to another tool. Follow `next_offset`, which advances by `visited_count`, including missing members; `returned_count` can be smaller.
|
|
108
|
-
|
|
109
|
-
## Parameter previews and atomic batches
|
|
110
|
-
|
|
111
|
-
- `set_parameters` accepts 1–200 updates, each isolated in a subtransaction. `preview` and `atomic` are independent and default to false. Preview still requires the intended model's `expected_document_id`.
|
|
112
|
-
- A preview commits an eligible transaction to exercise Revit's commit checks, then rolls back its enclosing transaction group. If an atomic batch has a failed update, or no updates are accepted, it rolls back without attempting commit. `commit_validation_performed` distinguishes these cases; do not claim commit validation when it is false. A returned preview result confirms group rollback; on an error, inspect the reported cleanup outcome.
|
|
113
|
-
- `succeeded` contains only committed updates. Previewed or otherwise rolled-back accepted steps appear in `proposed`, with `updated: 0` and `committed: false`; they are not changes left in the model. Both lists include zero-based input `index` and observed per-step `before`/`after` values. Repeated writes to the same parameter form a sequence, so intermediate `after` values are not a final-model snapshot. Read the element again when final values matter, and inspect all `failed` entries and commit warnings.
|
|
114
|
-
|
|
115
|
-
## Transform, delete, and change types
|
|
116
|
-
|
|
117
|
-
- Activate these tools with `find_revit_tools`. All three use host-document IDs and require `expected_document_id`, including previews. Inspect the common `committed`, `succeeded`, `proposed`, `failed`, `commitWarnings`, and `commit_validation_performed` fields. `updated` counts committed steps: a transform or deletion is one step for the whole selection, not one per element.
|
|
118
|
-
- `transform_elements` accepts 1–200 distinct `element_ids` and `action: "move"`, `"copy"`, or `"rotate"`. Supply `unit` explicitly: `millimeters`, `centimeters`, `meters`, `feet`, or `inches`. Move/copy uses `translation: [x,y,z]`; rotation uses `axis_origin`, a nonzero dimensionless `axis_direction`, and signed right-hand `angle_degrees`. Coordinates use the document's internal origin and axes, not a view or shared-coordinate frame. Returned location snapshots use feet regardless of input units.
|
|
119
|
-
- Transform applies the entire selection in one step. A failure rolls back that step; move/rotate rejects pinned requested elements, and the tool never unpins anything. Constraints or hosting can affect other elements. Snapshots do not provide a complete audit of those dependent effects. In copy previews, `created_ids` are temporary and marked `created_ids_are_temporary`; never reuse them after rollback.
|
|
120
|
-
- `delete_elements` accepts 1–200 distinct requested IDs and deletes them together. Pinned requested elements are rejected. A preview returns the complete `deleted_ids` set reported by `Document.Delete`, including `dependent_ids`, then rolls back. More than 10,000 deleted IDs causes rollback. This list does not audit surviving elements that Revit may modify.
|
|
121
|
-
- Optionally pass the full preview `deleted_ids` as `expected_deleted_ids` on a later deletion. The tool compares sets before commit and rolls back if they differ. This checks the deletion membership, not every element property or dependent effect. Use a new operation ID for the actual deletion because its arguments differ from the preview; retain the original ID only for an identical retry.
|
|
122
|
-
- `change_element_types` accepts 1–200 `updates` containing `element_id`/`type_id` pairs, with each target appearing once. Invalid types and pinned targets fail individually. Default batches can commit other targets; `atomic: true` rolls all back on any failure. After a committed change, use `resulting_id` and `unique_id`, since Revit can replace an element. Replacement IDs in `proposed` are temporary after either preview or atomic rollback and must not be reused. Constraints can affect connected or hosted elements beyond the returned target snapshots.
|
|
123
|
-
|
|
124
|
-
## Views, sheets, and placements
|
|
125
|
-
|
|
126
|
-
- Activate `manage_views`, `manage_sheets`, and `manage_sheet_placements` with `find_revit_tools`. They require `expected_document_id` and support `preview: true` for edits. Each edit is one step using the common transaction result fields. Created preview IDs are temporary (`id_is_temporary: true`); do not reuse them. Use `delete_elements` for removal and `open_view` to activate a committed view or sheet.
|
|
127
|
-
- `manage_views` supports `create_plan`, `create_3d`, `create_section`, `duplicate`, and `update`. Discover compatible `view_family_type_id` values with `get_element_types` using `of_class: "ViewFamilyType"`; use `get_elements` for levels and existing views. Plan creation also requires `level_id`; 3D creation is isometric. Duplicate/update requires `view_id`. Duplication options are `duplicate` (default), `with_detailing`, and `dependent`, subject to the view's supported options.
|
|
128
|
-
- A section requires `unit`, `origin`, nonzero orthogonal `viewing_direction` and `up` vectors, and positive `width`, `height`, and `depth`. Its origin and dimensions use the explicit length unit in the document's internal coordinate frame; directions are dimensionless. Width/height extend symmetrically about the origin, with depth extending along the viewing direction. This is not a sheet-coordinate operation.
|
|
129
|
-
- View edits can apply `name`, `scale` (1–24000), and compatible `view_template_id` together. Use template ID `-1` only when intentionally removing a template. An incompatible template or template-controlled scale fails the step; the tool does not remove a template automatically to change scale.
|
|
130
|
-
- `manage_sheets` creation requires `name` and `number`; optional `titleblock_type_id` selects a loaded titleblock `FamilySymbol`, and omission creates a sheet without one. Discover titleblock types with `get_element_types` and category `OST_TitleBlocks`. Update requires `sheet_id` and accepts name/number; it rejects `titleblock_type_id`. Use `change_element_types` on an existing titleblock instance to change its type.
|
|
131
|
-
- `manage_sheet_placements` `list` requires `sheet_id` and returns viewports and schedule instances with `offset`/`limit` (default 100, maximum 200) and `next_offset`. Listing does not edit the model, but the tool is classified as write-capable, so the exact document guard applies to every action.
|
|
132
|
-
- Placement `place` requires `sheet_id`, `view_id`, `position: [x,y,0]`, and explicit `unit`. `move` uses `placement_id`, position, and unit; pinned placements are rejected. Coordinates are paper-space sheet coordinates: never multiply them by view scale. A viewport's position is its box center excluding the label; a schedule's position is its insertion point. Returned positions always use feet and identify `position_kind`.
|
|
133
|
-
- Optional viewport `rotation` is `none`, `clockwise`, or `counterclockwise`. Omit rotation entirely for schedules, including when no rotation is intended. Revit validates placement eligibility; placeholder sheets cannot receive content, and a view already placed elsewhere may be rejected.
|
|
134
|
-
|
|
135
|
-
## Schedule authoring and tags
|
|
136
|
-
|
|
137
|
-
- `manage_schedules` creates or configures a regular schedule as one atomic step. Creation requires `category` and `name`; `area_scheme_id` is available for area schedules. Configure requires `schedule_id`; category and area scheme are creation-only. Use `is_itemized` to control whether instances remain separate. Templates, revision/embedded schedules, and calculated/combined-field authoring are outside this tool.
|
|
138
|
-
- Discover eligible additions with `get_schedule_fields` on an existing schedule: use optional localized `name_filter` and `offset`/`limit` (default 100, maximum 200). Each field is identified by the pair `parameter_id` and case-sensitive `field_type`; negative built-in parameter IDs are valid. `included` reports existing pairs. These are not the schedule-local `field_id` values returned by `get_schedules`.
|
|
139
|
-
- `add_fields` uses the eligible pair and supports heading, hidden state, and width. When Count appears in field discovery, pass its returned `parameter_id` and `field_type` unchanged. Count also supports `{ "field_type": "Count" }` without a parameter ID. A supplied pair must be eligible for that schedule; do not guess its parameter ID. `update_fields`, `sort_fields`, and `filters` instead use actual schedule-local `field_id` values from `get_schedules`; never guess them from column positions or parameter IDs. Read newly committed fields before configuring their sort/filter rules. A preview's new schedule and added field IDs are temporary and cannot be reused.
|
|
140
|
-
- Add/update arrays each allow 50 fields. Width changes require an explicit length `unit` and apply to both grid and sheet widths. Supplied `sort_fields` (maximum 4) or `filters` (maximum 8) replace the entire corresponding list; omission preserves it, and `[]` clears it. Sort entries support `descending` and `show_header`.
|
|
141
|
-
- Filters require `field_id`, `comparison` (`equals`, `not_equals`, `contains`, `greater_than`, or `less_than`), `value_type` (`string`, `number`, `integer`, or `element_id`), and matching `value`. Measured `number` values require an explicit compatible `unit`; omit units for unitless numbers. Use `get_schedules` field `spec_type_id`, `can_filter_value`, and `can_filter_substring` to inspect capabilities. Its numeric filter values use internal units and its comparison names are Revit enum names, not the write schema's comparison strings. Returned grid/sheet widths use feet.
|
|
142
|
-
- `create_tags` requires `kind` (`element`, `room`, `space`, or `area`), one `view_id`, loaded tag `FamilySymbol` `tag_type_id`, explicit length `unit`, and 1–100 targets containing `element_id` and `head_position: [x,y,z]`. Positions use the document's internal coordinates and always describe the tag head, including with `leader: true`; returned head positions use feet. Targets must belong to the host document; linked targets and face/subelement references are unsupported.
|
|
143
|
-
- Element tags accept `orientation: "horizontal"` (default) or `"vertical"`; omit orientation for spatial tags. Room/space/area tags require a compatible plan view and positions at the spatial element's level. Templates, perspective views, and unlocked 3D views cannot host these independent tags. Discover a compatible loaded tag type before creating tags.
|
|
144
|
-
- Schedule edits and tag creation require exact document targeting and support commit-validated preview rollback. Tag batches default to per-target partial success; `atomic: true` rolls all back on any failure. Inspect all common transaction fields. All tag IDs in `proposed` are temporary after preview or atomic rollback; only reuse IDs from committed `succeeded` entries.
|
|
145
|
-
|
|
146
|
-
## Links, schedules, and relationships
|
|
147
|
-
|
|
148
|
-
- Start linked queries with `get_linked_models`. It lists direct link instances, including unloaded links, with `link_instance_id`, `loaded`, and `linked_document_id`. Pass the returned linked identity unchanged as `expected_linked_document_id` to `get_linked_elements`; use the host overview's ID for `expected_document_id`. Refresh link discovery after a link is unloaded or reloaded. Nested links are not traversed.
|
|
149
|
-
- `get_linked_elements` accepts the same category, class, level, type, parameter filters, `count_only`, and `offset`/`limit` as `get_elements`, but does not support `in_active_view`. Level and type IDs belong to the linked document. Preserve each row's full `reference`, including the link instance: two placements of the same linked model can have different host coordinates. Linked element IDs must not be passed to host selection or write tools.
|
|
150
|
-
- With `include_bounds: true`, `get_linked_elements` returns `host_bounds` in internal feet, or null where no bounding box exists. These are axis-aligned host bounds calculated from all eight transformed box corners, not exact element geometry. Link transform origins are also in feet; the basis vectors describe orientation and scale.
|
|
151
|
-
- `get_schedules` without `schedule_id` lists schedules, optionally narrowed by `name_filter`; templates and titleblock revision schedules are excluded from this list. Supply a returned ID to read field definitions, specification and width metadata, filter capabilities, current sort/filter rules, and formatted body cells. Body rows may include headings, grouped entries, and totals: neither a row index nor a cell value establishes an element ID. Hidden field definitions do not correspond one-to-one to displayed columns.
|
|
152
|
-
- Schedule body reads have two independent continuations: `next_offset` for rows and `next_column_offset` for columns. Read all column pages at each row offset before advancing the rows. List/body pages default to 50 entries (maximum 200); column pages default to 50 (maximum 50). Use returned row and column indices rather than guessing their origin.
|
|
153
|
-
- `get_element_relationships` reads one host element. Choose `relationships` to narrow the result; each relationship has its own count and `next_offset`, using the supplied `offset`/`limit` (default 100, maximum 200). `host`, `parent`, and `subcomponents` describe family instances; `members` describes groups or assemblies. Links are not traversed. Logical `dependents` are not a complete prediction of what deletion would remove. In a family document, request specific kinds that exclude `joined`.
|
|
154
|
-
- Follow each tool's returned pagination separately from `read_revit_result`. Link-instance pages default to 100 (maximum 1000); linked-element pages default to 200 (maximum 1000).
|
|
155
|
-
|
|
156
|
-
## execute_csharp playbook
|
|
157
|
-
|
|
158
|
-
- Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), `inputs` (`System.Text.Json.JsonElement`), and `Dump(value)` to record intermediates into the result's `dumps[]`. Pass an optional JSON `inputs` object separately from `code`; omission gives an empty object. Read values with `inputs.GetProperty(...)` and validate them in the script. Inputs are not interpolated into source and may contain at most 100,000 JSON characters.
|
|
159
|
-
- The script runs inside one backend-owned transaction. Do not start another transaction on `doc` (sub-transactions are allowed). The tool checks commit status and attempts rollback on script failure; read its actual outcome instead of assuming rollback succeeded. Result projection can fail after a successful commit and report `returnValueError`. Filesystem and UI effects are separate from model rollback.
|
|
160
|
-
- Scripts must be fully synchronous: `await`/`async` is rejected at compile time; never block on `Task.Result`/`.Wait()`.
|
|
161
|
-
- Return primitives, strings, or anonymous objects/lists; raw Revit API objects are projected to compact shapes (Element -> `{id,name,category,typeName,levelId}`, ElementId -> number, XYZ -> `{x,y,z}`).
|
|
162
|
-
- Lengths are internal units (decimal feet) — convert with `UnitUtils.ConvertToInternalUnits`/`ConvertFromInternalUnits`.
|
|
163
|
-
- Common pitfalls: call `FamilySymbol.Activate()` before `NewFamilyInstance`; use collector-level filtering (`OfCategory`/`OfClass`/`WhereElementIsNotElementType`) and bounded loops. The budget is 120s and Revit cannot be interrupted mid-script. The dialog guard attempts dismissive responses and reports `suppressedDialogs`; it cannot guarantee handling every modal dialog.
|
|
164
|
-
- `capture_view` returns a `filePath` to a temp PNG, never image data — open it with the read tool to actually see it.
|
|
165
|
-
|
|
166
|
-
## Reusable scripts
|
|
167
|
-
|
|
168
|
-
- `manage_revit_scripts` supports `save`, `list`, `read`, `run`, and `history`. The local library is `%APPDATA%\pi-revit\scripts`, with immutable versions and run records. Save requires `name`, `description`, `code`, and `input_types`, and never executes the code. Names contain 1–64 lowercase letters, digits, underscores, or hyphens, starting with a letter. The returned 64-character SHA-256 `version` identifies the complete saved definition, including source, description, and input declarations.
|
|
169
|
-
- Read the exact `name`/`version` to inspect its code before running it; there is no implicit latest version. Run requires that exact saved hash, `expected_document_id`, and matching `inputs`. Saved content is integrity-checked before use. Library runs require a bridge with operation receipts; saving/reading/listing/history do not require a model.
|
|
170
|
-
- `input_types` declares at most 40 named required inputs of kind `string`, `number`, `integer`, `boolean`, `object`, or `array`. Extra inputs are rejected. This validates top-level JSON kinds only; scripts must validate nested contents, units, ranges, element identities, and other domain rules. `inputs` reaches the script as a separate `JsonElement`, not substituted source text.
|
|
171
|
-
- A library run has the same unrestricted model/UI/file/external effects and one-transaction behavior as `execute_csharp`, including its synchronous execution rule and timeout. There is no library preview mode or automatic model saving. Inspect the code's intended effects and honor the user's save instructions; saving a script definition is not saving the model.
|
|
172
|
-
- `list` and `history` support optional exact-name filtering and outer `offset`/`limit` (default 20, maximum 100). History records script version, document identity, input hash, timestamps, and the operation/bridge receipt identifiers, not raw input values or results. `prepared` is not proof of execution, `response_received` is not proof of a successful model edit, and `outcome_unconfirmed` needs receipt inspection. If interrupted, check `get_revit_operation` before retrying the same version, document, and inputs with the original `_operation_id`.
|
|
173
|
-
|
|
174
|
-
For example, save a numeric echo/check definition with `manage_revit_scripts`:
|
|
175
|
-
|
|
176
|
-
```json
|
|
177
|
-
{
|
|
178
|
-
"action": "save",
|
|
179
|
-
"name": "check_number",
|
|
180
|
-
"description": "Echo a supplied number and report whether it is nonnegative.",
|
|
181
|
-
"code": "var value = inputs.GetProperty(\"value\").GetDouble(); return new { value, nonnegative = value >= 0 };",
|
|
182
|
-
"input_types": { "value": "number" }
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
Read the returned version and inspect its source:
|
|
187
|
-
|
|
188
|
-
```json
|
|
189
|
-
{ "action": "read", "name": "check_number", "version": "<exact version from save>" }
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Then explicitly run that version against the intended open model, replacing both placeholders with the actual returned identities:
|
|
193
|
-
|
|
194
|
-
```json
|
|
195
|
-
{
|
|
196
|
-
"action": "run",
|
|
197
|
-
"name": "check_number",
|
|
198
|
-
"version": "<exact version from save>",
|
|
199
|
-
"expected_document_id": "<project.documentId from the current overview>",
|
|
200
|
-
"inputs": { "value": 12.5 }
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
This example's source only returns the supplied value and a comparison; it performs no model edit or save. The run still uses the normal script transaction and operation receipt.
|
|
205
|
-
|
|
206
|
-
## Operation receipts and retries
|
|
207
|
-
|
|
208
|
-
- With a supporting bridge, every bridge tool call automatically receives an operation ID, included in its response or request error. Use `get_revit_operation` with `operation_id` to inspect it without queueing a model action. Receipts are held by the bridge, not the Pi session; the original bridge must remain reachable. Receipt reads and identical retries route to that original session even if another instance is now selected.
|
|
209
|
-
- States are `queued`, `running`, `succeeded`, `failed`, `expired_before_start`, `result_unavailable`, or `unknown`. `expired_before_start` confirms no tool action began. `failed` and `result_unavailable` do not establish rollback; inspect the result and actual effects. `succeeded` describes call completion, not a committed model edit: a preview can succeed with `committed: false` and rolled-back `proposed` changes.
|
|
210
|
-
- Retry only an identical request with the exact `_operation_id` added to its original tool arguments. The bridge waits for or returns that operation's response without executing it again. Preserve the original tool, document identity, and every argument; a mismatch is rejected. Omitting `_operation_id` creates a new operation. Do not use a new ID to bypass an unresolved earlier outcome.
|
|
211
|
-
- Full results are bounded to 128 completed receipts and 32 MiB in total. Eviction leaves the ID and outcome reserved as a receipt record for the rest of that bridge session, with `result_available: false`; retrying cannot recreate the evicted result or rerun the action. At 10,000 records, new tracked calls are rejected while existing receipts remain queryable.
|
|
212
|
-
- Restarting the bridge clears receipts and changes its identity. An old operation ID cannot be replayed in the new session. An unavailable original bridge or an `unknown` receipt is not proof that the operation never ran. Changing selection does not redirect old receipts or retries. Inspect the original bridge/model before issuing a new edit. An older bridge without tracking provides no receipt guarantee.
|
|
213
|
-
|
|
214
|
-
## Failure modes
|
|
215
|
-
|
|
216
|
-
- **Identity rejected**: no tool action was performed. Verify the intended active model, refresh `project.documentId`, and pass `expected_document_id` unchanged.
|
|
217
|
-
- **Several instances / selected session unavailable**: use `manage_revit_instances` to list and explicitly select the intended current bridge, then refresh its model overview. Calls are not redirected automatically. For an old operation receipt, restore access to its original bridge or inspect the original model; choosing another session does not establish the old outcome.
|
|
218
|
-
- **Partial UI/export effects**: selection or zoom can already have changed when isolation fails. Export errors can leave incomplete files, and IFC commit warnings are returned. Inspect the reported effects and output paths; model rollback does not remove files or reverse earlier UI actions.
|
|
219
|
-
- **Large-result save failed**: Revit may already have completed the operation. Check `get_revit_operation` using its operation ID to recover a retained bridge result. Verify its effects before issuing a new write.
|
|
220
|
-
- **Bridge not reachable** ("Revit bridge is not available" / "Could not reach the Revit bridge"): Revit is not running or the add-in did not load. Ask the user to start Revit, then retry `ping`.
|
|
221
|
-
- **HTTP 409 / "No active Revit document is open."** (`hasActiveDocument: false`): Revit is running but no project is open. Ask the user to open a project, then retry. This fails immediately; do not wait or retry blindly.
|
|
222
|
-
- **Timeout** ("Revit did not answer within Ns", 30s default / 120s for execute_csharp, capture_view, export_documents): Revit is busy or showing a modal dialog. An already-started tool can still complete. Check `get_revit_operation`; reuse the exact `_operation_id` and original arguments for an identical retry. If no receipt is available, verify model state before a new write.
|
|
223
|
-
- **Cancelled**: client cancellation does not cancel bridge work. Check the operation receipt and follow the same retry rules; a queued call may still start before its deadline, and running work cannot be interrupted.
|
|
224
|
-
|
|
225
|
-
## Upgrading from 0.2.x
|
|
226
|
-
|
|
227
|
-
Version 0.3.0 requires exact document IDs for the operations above. Update the Pi package and deploy the matching add-in with Revit closed, then restart Revit and start a fresh Pi session so the new schemas and instructions are loaded. Obtain a fresh overview before writes. `ping` reports an installed/loaded version mismatch. Setup preserves existing workspace `AGENTS.md`; merge these targeting, result-reading, and folder rules into an older workspace's instructions when needed.
|
|
39
|
+
The bridge targets Revit 2025–2027. Manuals describe this package's contract; check the selected bridge's schema and version. `ping` and API search need no open document. Saved results and local script-library inspection work without Revit. General modeling standards and the future Revit subject library are separate from these tool contracts.
|