@get-bb/plugin-sdk 0.4.9 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -3,6 +3,8 @@ var runtime = globalThis.__bbPluginRuntime?.pluginSdkApp ?? {};
3
3
  var definePluginApp = runtime.definePluginApp;
4
4
  var ThreadChat = runtime.ThreadChat;
5
5
  var Markdown = runtime.Markdown;
6
+ var experimental_FileLink = runtime.experimental_FileLink;
7
+ var experimental_UrlLink = runtime.experimental_UrlLink;
6
8
  var experimental_NewThreadComposer = runtime.experimental_NewThreadComposer;
7
9
  var experimental_SourceCode = runtime.experimental_SourceCode;
8
10
  var experimental_Diff = runtime.experimental_Diff;
@@ -12,6 +14,8 @@ var useRealtimeConnectionState = runtime.useRealtimeConnectionState;
12
14
  var useSettings = runtime.useSettings;
13
15
  var useBbContext = runtime.useBbContext;
14
16
  var useBbNavigate = runtime.useBbNavigate;
17
+ var experimental_useAppPanel = runtime.experimental_useAppPanel;
18
+ var experimental_useFixedTabTarget = runtime.experimental_useFixedTabTarget;
15
19
  var useComposer = runtime.useComposer;
16
20
  var useComposerView = runtime.useComposerView;
17
21
  var experimental_useSidebarThreads = runtime.experimental_useSidebarThreads;
@@ -23,8 +27,12 @@ export {
23
27
  ThreadChat,
24
28
  definePluginApp,
25
29
  experimental_Diff,
30
+ experimental_FileLink,
26
31
  experimental_NewThreadComposer,
27
32
  experimental_SourceCode,
33
+ experimental_UrlLink,
34
+ experimental_useAppPanel,
35
+ experimental_useFixedTabTarget,
28
36
  experimental_useSidebarThreadActions,
29
37
  experimental_useSidebarThreadPullRequest,
30
38
  experimental_useSidebarThreadSplit,
@@ -0,0 +1,135 @@
1
+ // src/internal/file-navigation-validation.ts
2
+ var FILE_PATH_MAX_LENGTH = 32768;
3
+ var WINDOWS_DRIVE_ABSOLUTE_PATH = /^[A-Za-z]:[\\/]/u;
4
+ var WINDOWS_UNC_ABSOLUTE_PATH = /^\\\\/u;
5
+ function isJsonObject(value) {
6
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
7
+ return false;
8
+ }
9
+ const prototype = Object.getPrototypeOf(value);
10
+ return prototype === Object.prototype || prototype === null;
11
+ }
12
+ function hasExactKeys(value, keys) {
13
+ const actualKeys = Object.keys(value);
14
+ return actualKeys.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
15
+ }
16
+ function isNonEmptyIdentity(value) {
17
+ return typeof value === "string" && value.length > 0 && value.length <= FILE_PATH_MAX_LENGTH && value.trim() === value;
18
+ }
19
+ function hasControlCharacter(value) {
20
+ for (const character of value) {
21
+ const codePoint = character.codePointAt(0);
22
+ if (codePoint !== void 0 && codePoint < 32) return true;
23
+ }
24
+ return false;
25
+ }
26
+ function hasUnpairedSurrogate(value) {
27
+ for (let index = 0; index < value.length; index += 1) {
28
+ const codeUnit = value.charCodeAt(index);
29
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
30
+ if (index + 1 >= value.length) return true;
31
+ const nextCodeUnit = value.charCodeAt(index + 1);
32
+ if (nextCodeUnit < 56320 || nextCodeUnit > 57343) return true;
33
+ index += 1;
34
+ } else if (codeUnit >= 56320 && codeUnit <= 57343) {
35
+ return true;
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+ function isValidPathSegment(segment) {
41
+ return segment.length > 0 && segment !== "." && segment !== "..";
42
+ }
43
+ function isPositiveSafeInteger(value) {
44
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
45
+ }
46
+ function isValidRelativeFilePath(value) {
47
+ if (typeof value !== "string" || value.length === 0 || value.length > FILE_PATH_MAX_LENGTH || value.trim() !== value || value.includes("\\") || hasControlCharacter(value) || hasUnpairedSurrogate(value)) {
48
+ return false;
49
+ }
50
+ return value.split("/").every(isValidPathSegment);
51
+ }
52
+ function isValidAbsoluteHostFilePath(value) {
53
+ if (typeof value !== "string" || value.length === 0 || value.length > FILE_PATH_MAX_LENGTH || value.trim() !== value || hasControlCharacter(value) || hasUnpairedSurrogate(value)) {
54
+ return false;
55
+ }
56
+ if (value.startsWith("/") && !value.startsWith("//")) {
57
+ const segments = value.slice(1).split("/");
58
+ return segments.length > 0 && segments.every(isValidPathSegment);
59
+ }
60
+ if (WINDOWS_DRIVE_ABSOLUTE_PATH.test(value)) {
61
+ const segments = value.slice(3).split(/[\\/]/u);
62
+ return segments.length > 0 && segments.every(isValidPathSegment);
63
+ }
64
+ if (WINDOWS_UNC_ABSOLUTE_PATH.test(value)) {
65
+ const segments = value.slice(2).split(/[\\/]/u);
66
+ return segments.length >= 3 && segments.every(isValidPathSegment);
67
+ }
68
+ return false;
69
+ }
70
+ function normalizeExperimentalLiveFileTarget(value) {
71
+ if (!isJsonObject(value) || typeof value.kind !== "string") return null;
72
+ switch (value.kind) {
73
+ case "workspace":
74
+ if (!hasExactKeys(value, ["kind", "environmentId", "path"]) || !isNonEmptyIdentity(value.environmentId) || !isValidRelativeFilePath(value.path)) {
75
+ return null;
76
+ }
77
+ return {
78
+ kind: value.kind,
79
+ environmentId: value.environmentId,
80
+ path: value.path
81
+ };
82
+ case "host":
83
+ if (!hasExactKeys(value, ["kind", "hostId", "path"]) || !isNonEmptyIdentity(value.hostId) || !isValidAbsoluteHostFilePath(value.path)) {
84
+ return null;
85
+ }
86
+ return { kind: value.kind, hostId: value.hostId, path: value.path };
87
+ case "thread-storage":
88
+ if (!hasExactKeys(value, ["kind", "threadId", "path"]) || !isNonEmptyIdentity(value.threadId) || !isValidRelativeFilePath(value.path)) {
89
+ return null;
90
+ }
91
+ return { kind: value.kind, threadId: value.threadId, path: value.path };
92
+ default:
93
+ return null;
94
+ }
95
+ }
96
+ function normalizeExperimentalFileLocation(value) {
97
+ if (value === null) return null;
98
+ if (!isJsonObject(value) || typeof value.kind !== "string") return void 0;
99
+ switch (value.kind) {
100
+ case "line":
101
+ if (!hasExactKeys(value, ["kind", "line", "column"]) || !isPositiveSafeInteger(value.line) || value.column !== null && !isPositiveSafeInteger(value.column)) {
102
+ return void 0;
103
+ }
104
+ return {
105
+ kind: value.kind,
106
+ line: value.line,
107
+ column: value.column
108
+ };
109
+ case "range":
110
+ if (!hasExactKeys(value, ["kind", "startLine", "endLine"]) || !isPositiveSafeInteger(value.startLine) || !isPositiveSafeInteger(value.endLine) || value.endLine < value.startLine) {
111
+ return void 0;
112
+ }
113
+ return {
114
+ kind: value.kind,
115
+ startLine: value.startLine,
116
+ endLine: value.endLine
117
+ };
118
+ default:
119
+ return void 0;
120
+ }
121
+ }
122
+ function normalizeExperimentalFileOpenOptions(value) {
123
+ if (!isJsonObject(value) || !hasExactKeys(value, ["target", "location"])) {
124
+ return null;
125
+ }
126
+ const target = normalizeExperimentalLiveFileTarget(value.target);
127
+ const location = normalizeExperimentalFileLocation(value.location);
128
+ if (target === null || location === void 0) return null;
129
+ return { target, location };
130
+ }
131
+ export {
132
+ normalizeExperimentalFileLocation,
133
+ normalizeExperimentalFileOpenOptions,
134
+ normalizeExperimentalLiveFileTarget
135
+ };
@@ -54,6 +54,7 @@ var PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS = 4096;
54
54
  var PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES = 128 * 1024;
55
55
  var MENTION_PROVIDER_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
56
56
  var PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,63}$/;
57
+ var PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES = 64 * 1024;
57
58
  var SETTING_KEY_PATTERN = /^[a-zA-Z0-9_-]+$/;
58
59
  var settingsBaseFields = {
59
60
  label: z2.string().min(1),
@@ -255,6 +256,70 @@ function validateProviderLiteralArray(args) {
255
256
  }
256
257
  return Object.freeze(normalized);
257
258
  }
259
+ function normalizeProviderBridgeOptions(providerId, value) {
260
+ const active = /* @__PURE__ */ new Set();
261
+ function visit(current, path) {
262
+ if (current === null || typeof current === "string" || typeof current === "boolean") {
263
+ return current;
264
+ }
265
+ if (typeof current === "number") {
266
+ if (!Number.isFinite(current)) {
267
+ throw new Error(
268
+ `provider "${providerId}" experimental_bridgeOptions${path} must be finite JSON`
269
+ );
270
+ }
271
+ return current;
272
+ }
273
+ if (typeof current !== "object") {
274
+ throw new Error(
275
+ `provider "${providerId}" experimental_bridgeOptions${path} must be JSON`
276
+ );
277
+ }
278
+ if (active.has(current)) {
279
+ throw new Error(
280
+ `provider "${providerId}" experimental_bridgeOptions must not contain cycles`
281
+ );
282
+ }
283
+ active.add(current);
284
+ try {
285
+ if (Array.isArray(current)) {
286
+ const normalized3 = current.map(
287
+ (entry, index) => visit(entry, `${path}[${index}]`)
288
+ );
289
+ Object.freeze(normalized3);
290
+ return normalized3;
291
+ }
292
+ const prototype = Object.getPrototypeOf(current);
293
+ if (prototype !== Object.prototype && prototype !== null) {
294
+ throw new Error(
295
+ `provider "${providerId}" experimental_bridgeOptions${path} must contain only plain JSON objects`
296
+ );
297
+ }
298
+ const normalized2 = Object.fromEntries(
299
+ Object.entries(current).map(([key, entry]) => [
300
+ key,
301
+ visit(entry, `${path}.${key}`)
302
+ ])
303
+ );
304
+ Object.freeze(normalized2);
305
+ return normalized2;
306
+ } finally {
307
+ active.delete(current);
308
+ }
309
+ }
310
+ const normalized = visit(value, "");
311
+ if (normalized === null || Array.isArray(normalized) || typeof normalized !== "object") {
312
+ throw new Error(
313
+ `provider "${providerId}" experimental_bridgeOptions must be an object`
314
+ );
315
+ }
316
+ if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES) {
317
+ throw new Error(
318
+ `provider "${providerId}" experimental_bridgeOptions exceeds ${PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES} bytes`
319
+ );
320
+ }
321
+ return normalized;
322
+ }
258
323
  function validatePluginProviderDeclaration(declaration) {
259
324
  if (typeof declaration !== "object" || declaration === null) {
260
325
  throw new Error("provider declaration must be an object");
@@ -292,6 +357,24 @@ function validatePluginProviderDeclaration(declaration) {
292
357
  if (typeof capabilities !== "object" || capabilities === null) {
293
358
  throw new Error(`provider "${id}" capabilities must be an object`);
294
359
  }
360
+ const experimentalProviderHealth = capabilities.experimental_providerHealth ?? false;
361
+ const experimentalProviderUsage = capabilities.experimental_providerUsage ?? false;
362
+ const experimentalProviderInstallation = capabilities.experimental_providerInstallation ?? false;
363
+ if (typeof experimentalProviderHealth !== "boolean") {
364
+ throw new Error(
365
+ `provider "${id}" capabilities.experimental_providerHealth must be a boolean`
366
+ );
367
+ }
368
+ if (typeof experimentalProviderUsage !== "boolean") {
369
+ throw new Error(
370
+ `provider "${id}" capabilities.experimental_providerUsage must be a boolean`
371
+ );
372
+ }
373
+ if (typeof experimentalProviderInstallation !== "boolean") {
374
+ throw new Error(
375
+ `provider "${id}" capabilities.experimental_providerInstallation must be a boolean`
376
+ );
377
+ }
295
378
  const booleanCapabilityFields = [
296
379
  "supportsServiceTier",
297
380
  "supportsNativeUserQuestion",
@@ -313,6 +396,9 @@ function validatePluginProviderDeclaration(declaration) {
313
396
  );
314
397
  }
315
398
  const normalizedCapabilities = Object.freeze({
399
+ experimental_providerHealth: experimentalProviderHealth,
400
+ experimental_providerUsage: experimentalProviderUsage,
401
+ experimental_providerInstallation: experimentalProviderInstallation,
316
402
  supportsServiceTier: capabilities.supportsServiceTier,
317
403
  supportsNativeUserQuestion: capabilities.supportsNativeUserQuestion,
318
404
  fork: capabilities.fork,
@@ -342,10 +428,27 @@ function validatePluginProviderDeclaration(declaration) {
342
428
  allowed: PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES,
343
429
  requireNonEmpty: false
344
430
  });
431
+ const bridgeOptions = declaration.experimental_bridgeOptions === void 0 ? void 0 : normalizeProviderBridgeOptions(
432
+ id,
433
+ declaration.experimental_bridgeOptions
434
+ );
435
+ const visibility = declaration.experimental_visibility ?? "always";
436
+ if (visibility !== "always" && visibility !== "installed") {
437
+ throw new Error(
438
+ `provider "${id}" experimental_visibility must be "always" or "installed"`
439
+ );
440
+ }
441
+ if (visibility === "installed" && !normalizedCapabilities.experimental_providerHealth) {
442
+ throw new Error(
443
+ `provider "${id}" experimental_visibility "installed" requires experimental_providerHealth`
444
+ );
445
+ }
345
446
  return Object.freeze({
346
447
  id,
347
448
  displayName,
348
449
  ...icon === void 0 ? {} : { icon },
450
+ ...bridgeOptions === void 0 ? {} : { experimental_bridgeOptions: bridgeOptions },
451
+ experimental_visibility: visibility,
349
452
  capabilities: normalizedCapabilities,
350
453
  composerActions
351
454
  });
@@ -608,6 +711,7 @@ export {
608
711
  PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES,
609
712
  PLUGIN_HTTP_METHODS,
610
713
  PLUGIN_MENTION_TRIGGER_VALUES,
714
+ PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES,
611
715
  PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES,
612
716
  PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS,
613
717
  PLUGIN_PROVIDER_PERMISSION_MODE_VALUES,
@@ -276,6 +276,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
276
276
  const kind = "slots.navPanel";
277
277
  const id = requireSlotId(kind, registration?.id);
278
278
  requireUniqueId(kind, seenIds.navPanel, id);
279
+ const panelId = id;
279
280
  const path = requireNonEmptyString(kind, "path", registration.path);
280
281
  if (!PLUGIN_SLOT_ID_PATTERN.test(path)) {
281
282
  throw new Error(
@@ -311,8 +312,25 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
311
312
  `${fixedTabKind}: "layout" must be "padded" or "flush" when set`
312
313
  );
313
314
  }
315
+ const fixedTabPanelId = requireNonEmptyString(
316
+ fixedTabKind,
317
+ "panelId",
318
+ fixedTab?.panelId
319
+ );
320
+ if (fixedTabPanelId !== panelId) {
321
+ throw new Error(
322
+ `${fixedTabKind}: "panelId" must match its containing navPanel id ${JSON.stringify(panelId)}`
323
+ );
324
+ }
325
+ const experimentalTarget = fixedTab?.experimental_target;
326
+ if (experimentalTarget !== void 0 && (typeof experimentalTarget !== "object" || experimentalTarget === null || typeof Reflect.get(experimentalTarget, "validate") !== "function")) {
327
+ throw new Error(
328
+ `${fixedTabKind}: "experimental_target.validate" must be a function when set`
329
+ );
330
+ }
314
331
  return {
315
332
  id: id2,
333
+ panelId: fixedTabPanelId,
316
334
  title: requireNonEmptyString(
317
335
  fixedTabKind,
318
336
  "title",
@@ -324,7 +342,10 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
324
342
  fixedTab?.icon
325
343
  ),
326
344
  component: requireComponent(fixedTabKind, fixedTab?.component),
327
- ...layout === void 0 ? {} : { layout }
345
+ ...layout === void 0 ? {} : { layout },
346
+ ...experimentalTarget === void 0 ? {} : {
347
+ experimental_target: experimentalTarget
348
+ }
328
349
  };
329
350
  });
330
351
  })();