pi-revit 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/pi-revit/index.ts +27 -1
- package/package.json +1 -1
- package/scripts/build.ps1 +11 -0
- package/src/Revit/BridgeServer.cs +11 -1
- package/src/Revit/Tools/SearchApiDocs.cs +75 -15
|
@@ -3,6 +3,7 @@ import { Type, type TSchema } from "typebox";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { version as packageVersion } from "../../package.json";
|
|
6
7
|
|
|
7
8
|
interface BridgeInfo {
|
|
8
9
|
baseUrl: string;
|
|
@@ -195,6 +196,17 @@ function registerBridgeTool(pi: ExtensionAPI, descriptor: BridgeToolDescriptor)
|
|
|
195
196
|
});
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
/** `pi update --extensions` refreshes this package but not the deployed Revit add-in,
|
|
200
|
+
* so a newer extension can silently talk to an older bridge. The add-in reports the
|
|
201
|
+
* package version it was built from (stamped by scripts/build.ps1); any difference
|
|
202
|
+
* means the update is incomplete. */
|
|
203
|
+
function versionMismatch(addinVersion: unknown): string | null {
|
|
204
|
+
const addin = typeof addinVersion === "string" && addinVersion.length > 0 ? addinVersion : null;
|
|
205
|
+
if (addin === packageVersion) return null;
|
|
206
|
+
const state = addin ? `still runs version ${addin}` : "predates version reporting";
|
|
207
|
+
return `pi-revit ${packageVersion} is installed, but the Revit add-in ${state} — the update is incomplete. Close Revit and run: npx.cmd -y pi-revit (or ask the agent to run scripts\\deploy.ps1 from the installed package, then restart Revit).`;
|
|
208
|
+
}
|
|
209
|
+
|
|
198
210
|
function registerPing(pi: ExtensionAPI) {
|
|
199
211
|
pi.registerTool({
|
|
200
212
|
name: "ping",
|
|
@@ -206,8 +218,9 @@ function registerPing(pi: ExtensionAPI) {
|
|
|
206
218
|
executionMode: "sequential",
|
|
207
219
|
async execute(_toolCallId, _params, signal) {
|
|
208
220
|
const payload = await bridgeRequest("/ping", { method: "GET" }, signal, 10_000);
|
|
221
|
+
const warning = versionMismatch((payload as { addinVersion?: string }).addinVersion);
|
|
209
222
|
return {
|
|
210
|
-
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
223
|
+
content: [{ type: "text", text: JSON.stringify(payload) + (warning ? `\nWARNING: ${warning}` : "") }],
|
|
211
224
|
details: payload,
|
|
212
225
|
};
|
|
213
226
|
},
|
|
@@ -219,6 +232,19 @@ export default async function revitConnector(pi: ExtensionAPI) {
|
|
|
219
232
|
// bridge is down, so it is never part of /tools discovery.
|
|
220
233
|
registerPing(pi);
|
|
221
234
|
|
|
235
|
+
// Surface an incomplete update (see versionMismatch) once per session, right
|
|
236
|
+
// where the user lands after running `pi update --extensions`. Bridge down at
|
|
237
|
+
// session start is the normal Revit-closed case: stay quiet.
|
|
238
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
239
|
+
try {
|
|
240
|
+
const payload = (await bridgeRequest("/ping", { method: "GET" }, undefined, 3_000)) as { addinVersion?: string };
|
|
241
|
+
const warning = versionMismatch(payload.addinVersion);
|
|
242
|
+
if (warning) ctx.ui.notify(warning, "warning");
|
|
243
|
+
} catch {
|
|
244
|
+
// No bridge, no verdict.
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
222
248
|
let descriptors: BridgeToolDescriptor[];
|
|
223
249
|
try {
|
|
224
250
|
const payload = (await bridgeRequest("/tools", { method: "GET" }, undefined, DISCOVERY_TIMEOUT_MS)) as {
|
package/package.json
CHANGED
package/scripts/build.ps1
CHANGED
|
@@ -20,6 +20,17 @@ $ErrorActionPreference = 'Stop'
|
|
|
20
20
|
$project = Join-Path $PSScriptRoot '..\src\Revit\RevitBridge.csproj'
|
|
21
21
|
|
|
22
22
|
$buildArgs = @('build', $project, '-c', $Configuration)
|
|
23
|
+
|
|
24
|
+
# Stamp the package version into the assembly so the bridge can report which release
|
|
25
|
+
# the deployed add-in came from; the pi extension compares it against its own package
|
|
26
|
+
# version at session start to detect partial updates.
|
|
27
|
+
$packageJson = Join-Path $PSScriptRoot '..\package.json'
|
|
28
|
+
if (Test-Path $packageJson) {
|
|
29
|
+
$packageVersion = (Get-Content $packageJson -Raw | ConvertFrom-Json).version
|
|
30
|
+
if ($packageVersion) {
|
|
31
|
+
$buildArgs += "-p:Version=$packageVersion"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
23
34
|
if ($TargetFramework) {
|
|
24
35
|
# Override TargetFrameworks (plural) rather than TargetFramework: NuGet restore ignores a
|
|
25
36
|
# single -p:TargetFramework and still restores every framework the project declares, so a
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
using System.IO;
|
|
2
2
|
using System.Net;
|
|
3
3
|
using System.Net.Sockets;
|
|
4
|
+
using System.Reflection;
|
|
4
5
|
using System.Text;
|
|
5
6
|
using System.Text.Json;
|
|
6
7
|
|
|
@@ -22,6 +23,15 @@ namespace RevitBridge
|
|
|
22
23
|
{
|
|
23
24
|
private const int DefaultPort = 47777;
|
|
24
25
|
private const int MaxPort = 47797;
|
|
26
|
+
|
|
27
|
+
/// <summary>Package version stamped into the assembly by scripts/build.ps1. The pi
|
|
28
|
+
/// extension compares this against its own package version to detect partial
|
|
29
|
+
/// updates (npm package updated, add-in not redeployed); a build made without
|
|
30
|
+
/// the stamp reports its default assembly version instead.</summary>
|
|
31
|
+
private static readonly string AddinVersion =
|
|
32
|
+
typeof(BridgeServer).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion?.Split('+')[0]
|
|
33
|
+
?? typeof(BridgeServer).Assembly.GetName().Version?.ToString(3)
|
|
34
|
+
?? "unknown";
|
|
25
35
|
private const int MaxRequestBytes = 4_000_000;
|
|
26
36
|
private const int MaxContentChars = 12_000;
|
|
27
37
|
|
|
@@ -260,7 +270,7 @@ namespace RevitBridge
|
|
|
260
270
|
private async Task<(int Status, object Response)> RouteAsync(string method, string path, Dictionary<string, string> query, string body, CancellationToken token)
|
|
261
271
|
{
|
|
262
272
|
if (method == "GET" && path == "/ping")
|
|
263
|
-
return (200, new { ok = true, service = "revit-bridge", revitVersion = _revitVersion, pid = Environment.ProcessId });
|
|
273
|
+
return (200, new { ok = true, service = "revit-bridge", revitVersion = _revitVersion, pid = Environment.ProcessId, addinVersion = AddinVersion });
|
|
264
274
|
|
|
265
275
|
if (method == "GET" && path == "/tools")
|
|
266
276
|
{
|
|
@@ -21,16 +21,19 @@ namespace RevitBridge.Tools
|
|
|
21
21
|
{
|
|
22
22
|
private const int DefaultMaxResults = 10;
|
|
23
23
|
private const int MaxResultsCap = 50;
|
|
24
|
+
|
|
25
|
+
// The Max*Chars caps apply to the model-visible markdown only; the index and
|
|
26
|
+
// the details payload keep the full documentation text.
|
|
24
27
|
private const int MaxMarkdownChars = 6_000;
|
|
25
|
-
private const int MaxSummaryChars = 600;
|
|
26
28
|
private const int MaxRemarksChars = 600;
|
|
27
29
|
private const int MaxReturnsChars = 300;
|
|
28
30
|
private const int MaxParamDocChars = 240;
|
|
29
31
|
private const int MaxParamsPerMember = 10;
|
|
32
|
+
private const int MaxExceptionsPerMember = 8;
|
|
30
33
|
|
|
31
34
|
public string Name => "search_api_docs";
|
|
32
35
|
public string Label => "Search API Docs";
|
|
33
|
-
public string Description => "Search the offline Revit API documentation (the RevitAPI.xml and RevitAPIUI.xml files shipped with Revit) for types, methods, constructors, properties, fields, and events; every public API enum is fully searchable by value name (values the XML leaves undocumented are synthesized from the API assemblies). query is a single name or substring — e.g. 'FilteredElementCollector', 'Wall.Create', 'WALL_BASE_OFFSET' — ranked: exact name first, then prefix, then substring; dotted Type.Member queries match composites. Returns signatures with summary, remarks, parameter docs, return docs, and the Revit version a member was introduced in ('since'). Works with no document open. Use it to verify exact classes, members, and signatures before writing execute_csharp code. The first query builds the index (a few seconds); later queries are instant.";
|
|
36
|
+
public string Description => "Search the offline Revit API documentation (the RevitAPI.xml and RevitAPIUI.xml files shipped with Revit) for types, methods, constructors, properties, fields, and events; every public API enum is fully searchable by value name (values the XML leaves undocumented are synthesized from the API assemblies). query is a single name or substring — e.g. 'FilteredElementCollector', 'Wall.Create', 'WALL_BASE_OFFSET' — ranked: exact name first, then prefix, then substring; dotted Type.Member queries match composites, and same-named overloads rank simplest-first. To target one overload, continue the query past a parenthesis with parameter types, e.g. 'Wall.Create(Document, Curve'. Returns signatures with summary, remarks, parameter docs, return docs, exception docs, and the Revit version a member was introduced in ('since'); the top match shows its full docs inline. Works with no document open. Use it to verify exact classes, members, and signatures before writing execute_csharp code. The first query builds the index (a few seconds); later queries are instant.";
|
|
34
37
|
|
|
35
38
|
public bool RequiresDocument => false;
|
|
36
39
|
|
|
@@ -97,6 +100,9 @@ namespace RevitBridge.Tools
|
|
|
97
100
|
.Select(pair => new Dictionary<string, object?> { ["name"] = pair.Key, ["description"] = pair.Value })
|
|
98
101
|
.ToList(),
|
|
99
102
|
["returns"] = member.Returns,
|
|
103
|
+
["exceptions"] = member.Exceptions?
|
|
104
|
+
.Select(pair => new Dictionary<string, object?> { ["type"] = pair.Key, ["description"] = pair.Value })
|
|
105
|
+
.ToList(),
|
|
100
106
|
});
|
|
101
107
|
}
|
|
102
108
|
|
|
@@ -134,6 +140,7 @@ namespace RevitBridge.Tools
|
|
|
134
140
|
var top = scored
|
|
135
141
|
.OrderByDescending(entry => entry.Score)
|
|
136
142
|
.ThenBy(entry => entry.Member.Composite.Length)
|
|
143
|
+
.ThenBy(entry => entry.Member.ParameterCount)
|
|
137
144
|
.ThenBy(entry => entry.Member.FullName, StringComparer.Ordinal)
|
|
138
145
|
.Take(maxResults)
|
|
139
146
|
.Select(entry => entry.Member)
|
|
@@ -143,8 +150,15 @@ namespace RevitBridge.Tools
|
|
|
143
150
|
|
|
144
151
|
private static int Score(ApiMember member, string q, string[] words)
|
|
145
152
|
{
|
|
153
|
+
// A query with a parenthesis targets a specific overload by signature,
|
|
154
|
+
// e.g. 'wall.create(document, curve' — matched against the shortened
|
|
155
|
+
// signature text before any name-based ranking.
|
|
156
|
+
bool signatureQuery = q.Contains('(');
|
|
157
|
+
|
|
146
158
|
int score;
|
|
147
159
|
if (member.CompositeLower == q) score = 1000;
|
|
160
|
+
else if (signatureQuery && member.SignatureLower == q) score = 980;
|
|
161
|
+
else if (signatureQuery && member.SignatureLower.StartsWith(q, StringComparison.Ordinal)) score = 950;
|
|
148
162
|
else if (member.ShortNameLower == q) score = 900;
|
|
149
163
|
else if (member.CompositeLower.StartsWith(q, StringComparison.Ordinal)) score = 700;
|
|
150
164
|
else if (member.ShortNameLower.StartsWith(q, StringComparison.Ordinal)) score = 650;
|
|
@@ -181,7 +195,7 @@ namespace RevitBridge.Tools
|
|
|
181
195
|
}
|
|
182
196
|
markdown.Append(line);
|
|
183
197
|
if (i == 0)
|
|
184
|
-
AppendTopMatchDocs(markdown, member);
|
|
198
|
+
AppendTopMatchDocs(markdown, member, top.Count(other => other.CompositeLower == member.CompositeLower));
|
|
185
199
|
}
|
|
186
200
|
}
|
|
187
201
|
foreach (string warning in index.Warnings)
|
|
@@ -190,18 +204,23 @@ namespace RevitBridge.Tools
|
|
|
190
204
|
}
|
|
191
205
|
|
|
192
206
|
/// <summary>The model sees only this markdown — details.payload never reaches it —
|
|
193
|
-
/// so the top match carries its remarks, parameter, and
|
|
194
|
-
///
|
|
195
|
-
///
|
|
196
|
-
|
|
207
|
+
/// so the top match carries its remarks, parameter, return, and exception docs
|
|
208
|
+
/// inline (capped for display; the payload keeps full text). Lines that would blow
|
|
209
|
+
/// the markdown budget are dropped individually. When the top match is one of
|
|
210
|
+
/// several same-named overloads, a note says how to target another one.</summary>
|
|
211
|
+
private static void AppendTopMatchDocs(StringBuilder markdown, ApiMember member, int overloadCount)
|
|
197
212
|
{
|
|
198
|
-
var lines = new List<string>(
|
|
213
|
+
var lines = new List<string>(5);
|
|
199
214
|
if (member.Remarks is { } remarks)
|
|
200
|
-
lines.Add($"\n remarks: {remarks}");
|
|
215
|
+
lines.Add($"\n remarks: {Cap(remarks, MaxRemarksChars)}");
|
|
201
216
|
if (member.Parameters is { Count: > 0 } parameters)
|
|
202
|
-
lines.Add($"\n params: {string.Join("; ", parameters.Select(pair => $"{pair.Key} — {pair.Value}"))}");
|
|
217
|
+
lines.Add($"\n params: {string.Join("; ", parameters.Select(pair => $"{pair.Key} — {Cap(pair.Value, MaxParamDocChars)}"))}");
|
|
203
218
|
if (member.Returns is { } returns)
|
|
204
|
-
lines.Add($"\n returns: {returns}");
|
|
219
|
+
lines.Add($"\n returns: {Cap(returns, MaxReturnsChars)}");
|
|
220
|
+
if (member.Exceptions is { Count: > 0 } exceptions)
|
|
221
|
+
lines.Add($"\n throws: {string.Join("; ", exceptions.Select(pair => $"{pair.Key} — {Cap(pair.Value, MaxParamDocChars)}"))}");
|
|
222
|
+
if (overloadCount > 1)
|
|
223
|
+
lines.Add($"\n note: {member.Composite} has {overloadCount} overload(s) listed; full docs shown for the simplest top-ranked one. To target another, continue the query past a parenthesis with parameter types, e.g. '{member.Composite}(Document, '.");
|
|
205
224
|
foreach (string line in lines)
|
|
206
225
|
{
|
|
207
226
|
if (markdown.Length + line.Length > MaxMarkdownChars)
|
|
@@ -262,11 +281,18 @@ namespace RevitBridge.Tools
|
|
|
262
281
|
public required string FullNameLower { get; init; }
|
|
263
282
|
public required string CompositeLower { get; init; }
|
|
264
283
|
public required string ShortNameLower { get; init; }
|
|
284
|
+
public required string SignatureLower { get; init; }
|
|
285
|
+
|
|
286
|
+
/// <summary>Top-level parameter count from the doc id; overload tie-breaks
|
|
287
|
+
/// rank the simplest overload first.</summary>
|
|
288
|
+
public required int ParameterCount { get; init; }
|
|
289
|
+
|
|
265
290
|
public string? Summary { get; init; }
|
|
266
291
|
public string? Remarks { get; init; }
|
|
267
292
|
public string? Returns { get; init; }
|
|
268
293
|
public string? Since { get; init; }
|
|
269
294
|
public IReadOnlyList<KeyValuePair<string, string>>? Parameters { get; init; }
|
|
295
|
+
public IReadOnlyList<KeyValuePair<string, string>>? Exceptions { get; init; }
|
|
270
296
|
}
|
|
271
297
|
|
|
272
298
|
private sealed class DocIndex
|
|
@@ -364,6 +390,8 @@ namespace RevitBridge.Tools
|
|
|
364
390
|
FullNameLower = fullName.ToLowerInvariant(),
|
|
365
391
|
CompositeLower = composite.ToLowerInvariant(),
|
|
366
392
|
ShortNameLower = name.ToLowerInvariant(),
|
|
393
|
+
SignatureLower = composite.ToLowerInvariant(),
|
|
394
|
+
ParameterCount = 0,
|
|
367
395
|
Summary = enumType == typeof(BuiltInCategory)
|
|
368
396
|
? "BuiltInCategory enum value — usable as the 'category' argument of get_elements/get_element_types and with FilteredElementCollector.OfCategory in execute_csharp."
|
|
369
397
|
: $"{typeName} enum value (not documented in the XML; synthesized from {assemblyName} metadata).",
|
|
@@ -481,12 +509,24 @@ namespace RevitBridge.Tools
|
|
|
481
509
|
if (parameters is { Count: >= MaxParamsPerMember })
|
|
482
510
|
break;
|
|
483
511
|
string? name = param.Attribute("name")?.Value;
|
|
484
|
-
string? text =
|
|
512
|
+
string? text = CleanDocText(param);
|
|
485
513
|
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(text))
|
|
486
514
|
continue;
|
|
487
515
|
(parameters ??= new List<KeyValuePair<string, string>>()).Add(new KeyValuePair<string, string>(name, text));
|
|
488
516
|
}
|
|
489
517
|
|
|
518
|
+
List<KeyValuePair<string, string>>? exceptions = null;
|
|
519
|
+
foreach (var exception in element.Elements("exception"))
|
|
520
|
+
{
|
|
521
|
+
if (exceptions is { Count: >= MaxExceptionsPerMember })
|
|
522
|
+
break;
|
|
523
|
+
string? cref = exception.Attribute("cref")?.Value;
|
|
524
|
+
string? text = CleanDocText(exception);
|
|
525
|
+
if (string.IsNullOrEmpty(cref) || string.IsNullOrEmpty(text))
|
|
526
|
+
continue;
|
|
527
|
+
(exceptions ??= new List<KeyValuePair<string, string>>()).Add(new KeyValuePair<string, string>(ShortCref(cref), text));
|
|
528
|
+
}
|
|
529
|
+
|
|
490
530
|
return new ApiMember
|
|
491
531
|
{
|
|
492
532
|
Kind = kind,
|
|
@@ -498,14 +538,34 @@ namespace RevitBridge.Tools
|
|
|
498
538
|
FullNameLower = path.ToLowerInvariant(),
|
|
499
539
|
CompositeLower = composite.ToLowerInvariant(),
|
|
500
540
|
ShortNameLower = shortName.ToLowerInvariant(),
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
541
|
+
SignatureLower = signature.ToLowerInvariant(),
|
|
542
|
+
ParameterCount = CountTopLevelParameters(paramText),
|
|
543
|
+
Summary = CleanDocText(element.Element("summary")),
|
|
544
|
+
Remarks = CleanDocText(element.Element("remarks")),
|
|
545
|
+
Returns = CleanDocText(element.Element("returns")),
|
|
504
546
|
Since = CleanDocText(element.Element("since")),
|
|
505
547
|
Parameters = parameters,
|
|
548
|
+
Exceptions = exceptions,
|
|
506
549
|
};
|
|
507
550
|
}
|
|
508
551
|
|
|
552
|
+
/// <summary>Parameter count of a doc-id parameter list; commas inside generic
|
|
553
|
+
/// braces (Dictionary{K,V}) do not separate parameters.</summary>
|
|
554
|
+
private static int CountTopLevelParameters(string? paramText)
|
|
555
|
+
{
|
|
556
|
+
if (string.IsNullOrEmpty(paramText))
|
|
557
|
+
return 0;
|
|
558
|
+
int count = 1;
|
|
559
|
+
int depth = 0;
|
|
560
|
+
foreach (char c in paramText)
|
|
561
|
+
{
|
|
562
|
+
if (c == '{') depth++;
|
|
563
|
+
else if (c == '}') depth--;
|
|
564
|
+
else if (c == ',' && depth == 0) count++;
|
|
565
|
+
}
|
|
566
|
+
return count;
|
|
567
|
+
}
|
|
568
|
+
|
|
509
569
|
// ------------------------------------------------------------- text utils
|
|
510
570
|
|
|
511
571
|
/// <summary>Flattens doc XML to plain text: see/seealso cref -> short type name,
|