@get-bb/plugin-sdk 0.4.10 → 0.4.12

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.
@@ -982,6 +982,7 @@ function TestDiff({
982
982
  view = "unified",
983
983
  overflow = "scroll",
984
984
  showLineNumbers = true,
985
+ experimental_fullFileContents,
985
986
  className
986
987
  }) {
987
988
  return /* @__PURE__ */ jsx(
@@ -992,6 +993,7 @@ function TestDiff({
992
993
  "data-view": view,
993
994
  "data-overflow": overflow,
994
995
  "data-show-line-numbers": showLineNumbers ? "true" : "false",
996
+ "data-has-full-file-contents": experimental_fullFileContents === void 0 ? "false" : "true",
995
997
  className,
996
998
  children: patch
997
999
  }
@@ -1090,6 +1092,9 @@ var testPluginSdkApp = {
1090
1092
  experimental_useSidebarThreads() {
1091
1093
  return useSlotEnv("experimental_useSidebarThreads").sidebarThreads;
1092
1094
  },
1095
+ experimental_useProviders() {
1096
+ return useSlotEnv("experimental_useProviders").providers;
1097
+ },
1093
1098
  experimental_useSidebarThreadActions() {
1094
1099
  return useSlotEnv("experimental_useSidebarThreadActions").sidebarActions;
1095
1100
  },
@@ -1396,6 +1401,10 @@ function renderSlot(registration, props, options = {}) {
1396
1401
  threads: options.sidebarThreads?.threads ?? [],
1397
1402
  projects: options.sidebarThreads?.projects ?? []
1398
1403
  };
1404
+ const providers = {
1405
+ status: options.providers?.status ?? "ready",
1406
+ providers: options.providers?.providers ?? []
1407
+ };
1399
1408
  const sidebarActions = {
1400
1409
  open(threadId2, openOptions) {
1401
1410
  sidebarActionCalls.push({
@@ -1579,7 +1588,8 @@ ${block}
1579
1588
  sidebarThreads,
1580
1589
  sidebarActions,
1581
1590
  sidebarActionCalls,
1582
- sidebarPullRequests
1591
+ sidebarPullRequests,
1592
+ providers
1583
1593
  };
1584
1594
  const releaseComposerOwnership = () => {
1585
1595
  if (!composerOwnership.active) return;
@@ -268,7 +268,7 @@ function validateProviderLiteralArray(args) {
268
268
  }
269
269
  return Object.freeze(normalized);
270
270
  }
271
- function normalizeProviderBridgeOptions(providerId, value) {
271
+ function normalizeProviderBridgeOptions(providerId, value, label = "experimental_bridgeOptions") {
272
272
  const active = /* @__PURE__ */ new Set();
273
273
  function visit(current, path) {
274
274
  if (current === null || typeof current === "string" || typeof current === "boolean") {
@@ -277,14 +277,14 @@ function normalizeProviderBridgeOptions(providerId, value) {
277
277
  if (typeof current === "number") {
278
278
  if (!Number.isFinite(current)) {
279
279
  throw new Error(
280
- `provider "${providerId}" experimental_bridgeOptions${path} must be finite JSON`
280
+ `provider "${providerId}" ${label}${path} must be finite JSON`
281
281
  );
282
282
  }
283
283
  return current;
284
284
  }
285
285
  if (typeof current !== "object") {
286
286
  throw new Error(
287
- `provider "${providerId}" experimental_bridgeOptions${path} must be JSON`
287
+ `provider "${providerId}" ${label}${path} must be JSON`
288
288
  );
289
289
  }
290
290
  if (active.has(current)) {
@@ -304,7 +304,7 @@ function normalizeProviderBridgeOptions(providerId, value) {
304
304
  const prototype = Object.getPrototypeOf(current);
305
305
  if (prototype !== Object.prototype && prototype !== null) {
306
306
  throw new Error(
307
- `provider "${providerId}" experimental_bridgeOptions${path} must contain only plain JSON objects`
307
+ `provider "${providerId}" ${label}${path} must contain only plain JSON objects`
308
308
  );
309
309
  }
310
310
  const normalized2 = Object.fromEntries(
@@ -322,16 +322,329 @@ function normalizeProviderBridgeOptions(providerId, value) {
322
322
  const normalized = visit(value, "");
323
323
  if (normalized === null || Array.isArray(normalized) || typeof normalized !== "object") {
324
324
  throw new Error(
325
- `provider "${providerId}" experimental_bridgeOptions must be an object`
325
+ `provider "${providerId}" ${label} must be an object`
326
326
  );
327
327
  }
328
328
  if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES) {
329
329
  throw new Error(
330
- `provider "${providerId}" experimental_bridgeOptions exceeds ${PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES} bytes`
330
+ `provider "${providerId}" ${label} exceeds ${PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES} bytes`
331
331
  );
332
332
  }
333
333
  return normalized;
334
334
  }
335
+ var PROVIDER_STRING_MAX_CHARS = 512;
336
+ var PROVIDER_EXTENSION_KIND_NAME_PATTERN = /^[a-z0-9-]+$/u;
337
+ var PROVIDER_EXTENSION_KINDS_MAX = 32;
338
+ function requireNonBlankString(args) {
339
+ const { providerId, field, value } = args;
340
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > PROVIDER_STRING_MAX_CHARS) {
341
+ throw new Error(
342
+ `provider "${providerId}" ${field} must be a non-blank string of at most ${PROVIDER_STRING_MAX_CHARS} characters`
343
+ );
344
+ }
345
+ return value;
346
+ }
347
+ function validateProviderStrings(providerId, value) {
348
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
349
+ throw new Error(
350
+ `provider "${providerId}" experimental_strings must be an object`
351
+ );
352
+ }
353
+ const record = Object.fromEntries(
354
+ Object.entries(value)
355
+ );
356
+ const required = (field) => requireNonBlankString({
357
+ providerId,
358
+ field: `experimental_strings.${field}`,
359
+ value: record[field]
360
+ });
361
+ const optional = (field) => record[field] === void 0 ? void 0 : requireNonBlankString({
362
+ providerId,
363
+ field: `experimental_strings.${field}`,
364
+ value: record[field]
365
+ });
366
+ let iconTint;
367
+ if (record.iconTint !== void 0) {
368
+ const tint = record.iconTint;
369
+ if (typeof tint !== "object" || tint === null || Array.isArray(tint)) {
370
+ throw new Error(
371
+ `provider "${providerId}" experimental_strings.iconTint must be { light, dark }`
372
+ );
373
+ }
374
+ const tintRecord = Object.fromEntries(
375
+ Object.entries(tint)
376
+ );
377
+ iconTint = Object.freeze({
378
+ light: requireNonBlankString({
379
+ providerId,
380
+ field: "experimental_strings.iconTint.light",
381
+ value: tintRecord.light
382
+ }),
383
+ dark: requireNonBlankString({
384
+ providerId,
385
+ field: "experimental_strings.iconTint.dark",
386
+ value: tintRecord.dark
387
+ })
388
+ });
389
+ }
390
+ const brandPrefix = optional("brandPrefix");
391
+ const planModeCopy = optional("planModeCopy");
392
+ return Object.freeze({
393
+ signInHint: required("signInHint"),
394
+ expiredHint: required("expiredHint"),
395
+ installUrl: required("installUrl"),
396
+ ...brandPrefix === void 0 ? {} : { brandPrefix },
397
+ ...planModeCopy === void 0 ? {} : { planModeCopy },
398
+ ...iconTint === void 0 ? {} : { iconTint }
399
+ });
400
+ }
401
+ function validateProviderOptionDescriptors(args) {
402
+ const { providerId, field, value } = args;
403
+ if (!Array.isArray(value) || value.length === 0) {
404
+ throw new Error(
405
+ `provider "${providerId}" ${field} must be a non-empty array of { id, label, description? }`
406
+ );
407
+ }
408
+ const seen = /* @__PURE__ */ new Set();
409
+ const normalized = value.map(
410
+ (entry, index) => {
411
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
412
+ throw new Error(
413
+ `provider "${providerId}" ${field}[${index}] must be { id, label, description? }`
414
+ );
415
+ }
416
+ const record = Object.fromEntries(
417
+ Object.entries(entry)
418
+ );
419
+ const id = requireNonBlankString({
420
+ providerId,
421
+ field: `${field}[${index}].id`,
422
+ value: record.id
423
+ });
424
+ if (seen.has(id)) {
425
+ throw new Error(
426
+ `provider "${providerId}" ${field} id ${JSON.stringify(id)} is duplicated`
427
+ );
428
+ }
429
+ seen.add(id);
430
+ const label = requireNonBlankString({
431
+ providerId,
432
+ field: `${field}[${index}].label`,
433
+ value: record.label
434
+ });
435
+ const description = record.description === void 0 ? void 0 : requireNonBlankString({
436
+ providerId,
437
+ field: `${field}[${index}].description`,
438
+ value: record.description
439
+ });
440
+ return Object.freeze({
441
+ id,
442
+ label,
443
+ ...description === void 0 ? {} : { description }
444
+ });
445
+ }
446
+ );
447
+ return Object.freeze(normalized);
448
+ }
449
+ function validateProviderExtensionKinds(providerId, value) {
450
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
451
+ throw new Error(
452
+ `provider "${providerId}" experimental_extensionKinds must be an object keyed by kind name`
453
+ );
454
+ }
455
+ const entries = Object.entries(value);
456
+ if (entries.length > PROVIDER_EXTENSION_KINDS_MAX) {
457
+ throw new Error(
458
+ `provider "${providerId}" experimental_extensionKinds declares more than ${PROVIDER_EXTENSION_KINDS_MAX} kinds`
459
+ );
460
+ }
461
+ const normalized = {};
462
+ for (const [name, declaration] of entries) {
463
+ if (!PROVIDER_EXTENSION_KIND_NAME_PATTERN.test(name)) {
464
+ throw new Error(
465
+ `provider "${providerId}" experimental_extensionKinds name ${JSON.stringify(name)} must match ${PROVIDER_EXTENSION_KIND_NAME_PATTERN}`
466
+ );
467
+ }
468
+ if (typeof declaration !== "object" || declaration === null || Array.isArray(declaration)) {
469
+ throw new Error(
470
+ `provider "${providerId}" experimental_extensionKinds.${name} must be { item?, state? }`
471
+ );
472
+ }
473
+ const item = Reflect.get(declaration, "item");
474
+ const state = Reflect.get(declaration, "state");
475
+ if (item === void 0 && state === void 0) {
476
+ throw new Error(
477
+ `provider "${providerId}" experimental_extensionKinds.${name} must declare an item schema, a state schema, or both`
478
+ );
479
+ }
480
+ if (item !== void 0 && !isStandardSchema(item)) {
481
+ throw new Error(
482
+ `provider "${providerId}" experimental_extensionKinds.${name}.item must be a Standard Schema v1 validator`
483
+ );
484
+ }
485
+ if (state !== void 0 && !isStandardSchema(state)) {
486
+ throw new Error(
487
+ `provider "${providerId}" experimental_extensionKinds.${name}.state must be a Standard Schema v1 validator`
488
+ );
489
+ }
490
+ normalized[name] = Object.freeze({
491
+ ...item === void 0 ? {} : { item },
492
+ ...state === void 0 ? {} : { state }
493
+ });
494
+ }
495
+ return Object.freeze(normalized);
496
+ }
497
+ var PROVIDER_FALLBACK_MODELS_MAX = 64;
498
+ var PROVIDER_ENV_PASSTHROUGH_MAX = 32;
499
+ var PROVIDER_ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/u;
500
+ function validateProviderEnvPassthrough(providerId, value) {
501
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
502
+ throw new Error(
503
+ `provider "${providerId}" experimental_env must be { passthrough: [...] }`
504
+ );
505
+ }
506
+ const passthrough = Reflect.get(value, "passthrough");
507
+ if (!Array.isArray(passthrough)) {
508
+ throw new Error(
509
+ `provider "${providerId}" experimental_env.passthrough must be an array of variable names`
510
+ );
511
+ }
512
+ if (passthrough.length > PROVIDER_ENV_PASSTHROUGH_MAX) {
513
+ throw new Error(
514
+ `provider "${providerId}" experimental_env.passthrough names more than ${PROVIDER_ENV_PASSTHROUGH_MAX} variables`
515
+ );
516
+ }
517
+ const seen = /* @__PURE__ */ new Set();
518
+ for (const name of passthrough) {
519
+ if (typeof name !== "string" || !PROVIDER_ENV_NAME_PATTERN.test(name)) {
520
+ throw new Error(
521
+ `provider "${providerId}" experimental_env.passthrough entries must match ${PROVIDER_ENV_NAME_PATTERN}`
522
+ );
523
+ }
524
+ if (seen.has(name)) {
525
+ throw new Error(
526
+ `provider "${providerId}" experimental_env.passthrough repeats ${JSON.stringify(name)}`
527
+ );
528
+ }
529
+ seen.add(name);
530
+ }
531
+ return Object.freeze([...seen]);
532
+ }
533
+ function validateProviderFallbackModels(providerId, value) {
534
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
535
+ throw new Error(
536
+ `provider "${providerId}" experimental_models must be { fallback: [...] }`
537
+ );
538
+ }
539
+ const fallback = Reflect.get(value, "fallback");
540
+ if (!Array.isArray(fallback)) {
541
+ throw new Error(
542
+ `provider "${providerId}" experimental_models.fallback must be an array`
543
+ );
544
+ }
545
+ if (fallback.length > PROVIDER_FALLBACK_MODELS_MAX) {
546
+ throw new Error(
547
+ `provider "${providerId}" experimental_models.fallback lists more than ${PROVIDER_FALLBACK_MODELS_MAX} models`
548
+ );
549
+ }
550
+ const seen = /* @__PURE__ */ new Set();
551
+ let defaults = 0;
552
+ const normalized = fallback.map((entry, index) => {
553
+ const field = `experimental_models.fallback[${index}]`;
554
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
555
+ throw new Error(`provider "${providerId}" ${field} must be an object`);
556
+ }
557
+ const record = Object.fromEntries(
558
+ Object.entries(entry)
559
+ );
560
+ const id = requireNonBlankString({
561
+ providerId,
562
+ field: `${field}.id`,
563
+ value: record.id
564
+ });
565
+ if (seen.has(id)) {
566
+ throw new Error(
567
+ `provider "${providerId}" experimental_models.fallback id ${JSON.stringify(id)} is duplicated`
568
+ );
569
+ }
570
+ seen.add(id);
571
+ const displayName = requireNonBlankString({
572
+ providerId,
573
+ field: `${field}.displayName`,
574
+ value: record.displayName
575
+ });
576
+ const description = requireNonBlankString({
577
+ providerId,
578
+ field: `${field}.description`,
579
+ value: record.description
580
+ });
581
+ const efforts = record.supportedReasoningEfforts;
582
+ if (!Array.isArray(efforts) || efforts.length === 0) {
583
+ throw new Error(
584
+ `provider "${providerId}" ${field}.supportedReasoningEfforts must be a non-empty array`
585
+ );
586
+ }
587
+ const levels = /* @__PURE__ */ new Set();
588
+ const supportedReasoningEfforts = efforts.map(
589
+ (effort, effortIndex) => {
590
+ if (typeof effort !== "object" || effort === null || Array.isArray(effort)) {
591
+ throw new Error(
592
+ `provider "${providerId}" ${field}.supportedReasoningEfforts[${effortIndex}] must be { reasoningEffort, description }`
593
+ );
594
+ }
595
+ const reasoningEffort = Reflect.get(effort, "reasoningEffort");
596
+ if (typeof reasoningEffort !== "string" || !PLUGIN_PROVIDER_REASONING_LEVEL_VALUES.includes(
597
+ reasoningEffort
598
+ )) {
599
+ throw new Error(
600
+ `provider "${providerId}" ${field}.supportedReasoningEfforts[${effortIndex}].reasoningEffort must be one of ${PLUGIN_PROVIDER_REASONING_LEVEL_VALUES.join(", ")}`
601
+ );
602
+ }
603
+ const level = reasoningEffort;
604
+ if (levels.has(level)) {
605
+ throw new Error(
606
+ `provider "${providerId}" ${field}.supportedReasoningEfforts repeats ${JSON.stringify(level)}`
607
+ );
608
+ }
609
+ levels.add(level);
610
+ return Object.freeze({
611
+ reasoningEffort: level,
612
+ description: requireNonBlankString({
613
+ providerId,
614
+ field: `${field}.supportedReasoningEfforts[${effortIndex}].description`,
615
+ value: Reflect.get(effort, "description")
616
+ })
617
+ });
618
+ }
619
+ );
620
+ const defaultReasoningEffort = record.defaultReasoningEffort;
621
+ if (typeof defaultReasoningEffort !== "string" || !levels.has(defaultReasoningEffort)) {
622
+ throw new Error(
623
+ `provider "${providerId}" ${field}.defaultReasoningEffort must be one of its supportedReasoningEfforts`
624
+ );
625
+ }
626
+ if (typeof record.isDefault !== "boolean") {
627
+ throw new Error(
628
+ `provider "${providerId}" ${field}.isDefault must be a boolean`
629
+ );
630
+ }
631
+ if (record.isDefault) defaults += 1;
632
+ return Object.freeze({
633
+ id,
634
+ displayName,
635
+ description,
636
+ supportedReasoningEfforts: Object.freeze(supportedReasoningEfforts),
637
+ defaultReasoningEffort,
638
+ isDefault: record.isDefault
639
+ });
640
+ });
641
+ if (normalized.length > 0 && defaults !== 1) {
642
+ throw new Error(
643
+ `provider "${providerId}" experimental_models.fallback must mark exactly one model isDefault (found ${defaults})`
644
+ );
645
+ }
646
+ return Object.freeze(normalized);
647
+ }
335
648
  function validatePluginProviderDeclaration(declaration) {
336
649
  if (typeof declaration !== "object" || declaration === null) {
337
650
  throw new Error("provider declaration must be an object");
@@ -342,6 +655,12 @@ function validatePluginProviderDeclaration(declaration) {
342
655
  `invalid provider id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, and "-", starting with a letter or digit`
343
656
  );
344
657
  }
658
+ const family = declaration.experimental_family;
659
+ if (family !== void 0 && (typeof family !== "string" || !PROVIDER_ID_PATTERN.test(family))) {
660
+ throw new Error(
661
+ `provider "${id}" experimental_family must use the provider id grammar (2-64 lowercase letters, digits, and "-")`
662
+ );
663
+ }
345
664
  const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
346
665
  if (displayName.length === 0 || displayName.length > PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS) {
347
666
  throw new Error(
@@ -392,8 +711,7 @@ function validatePluginProviderDeclaration(declaration) {
392
711
  "supportsNativeUserQuestion",
393
712
  "supportsManualCompaction",
394
713
  "supportsThreadArchive",
395
- "supportsThreadRename",
396
- "supportsWorkflows"
714
+ "supportsThreadRename"
397
715
  ];
398
716
  for (const field of booleanCapabilityFields) {
399
717
  if (typeof capabilities[field] !== "boolean") {
@@ -417,7 +735,6 @@ function validatePluginProviderDeclaration(declaration) {
417
735
  supportsManualCompaction: capabilities.supportsManualCompaction,
418
736
  supportsThreadArchive: capabilities.supportsThreadArchive,
419
737
  supportsThreadRename: capabilities.supportsThreadRename,
420
- supportsWorkflows: capabilities.supportsWorkflows,
421
738
  permissionModes: validateProviderLiteralArray({
422
739
  providerId: id,
423
740
  field: "capabilities.permissionModes",
@@ -455,14 +772,45 @@ function validatePluginProviderDeclaration(declaration) {
455
772
  `provider "${id}" experimental_visibility "installed" requires experimental_providerHealth`
456
773
  );
457
774
  }
775
+ const strings = declaration.experimental_strings === void 0 ? void 0 : validateProviderStrings(id, declaration.experimental_strings);
776
+ const serviceTiers = declaration.experimental_serviceTiers === void 0 ? void 0 : validateProviderOptionDescriptors({
777
+ providerId: id,
778
+ field: "experimental_serviceTiers",
779
+ value: declaration.experimental_serviceTiers
780
+ });
781
+ const reasoningLevels = declaration.experimental_reasoningLevels === void 0 ? void 0 : validateProviderOptionDescriptors({
782
+ providerId: id,
783
+ field: "experimental_reasoningLevels",
784
+ value: declaration.experimental_reasoningLevels
785
+ });
786
+ const extensionKinds = declaration.experimental_extensionKinds === void 0 ? void 0 : validateProviderExtensionKinds(
787
+ id,
788
+ declaration.experimental_extensionKinds
789
+ );
790
+ const fallbackModels = declaration.experimental_models === void 0 ? void 0 : validateProviderFallbackModels(id, declaration.experimental_models);
791
+ const envPassthrough = declaration.experimental_env === void 0 ? void 0 : validateProviderEnvPassthrough(id, declaration.experimental_env);
792
+ const deriveProviderOptions = declaration.experimental_deriveProviderOptions;
793
+ if (deriveProviderOptions !== void 0 && typeof deriveProviderOptions !== "function") {
794
+ throw new Error(
795
+ `provider "${id}" experimental_deriveProviderOptions must be a function (context) => providerOptions`
796
+ );
797
+ }
458
798
  return Object.freeze({
459
799
  id,
460
800
  displayName,
801
+ ...family === void 0 ? {} : { experimental_family: family },
461
802
  ...icon === void 0 ? {} : { icon },
462
803
  ...bridgeOptions === void 0 ? {} : { experimental_bridgeOptions: bridgeOptions },
463
804
  experimental_visibility: visibility,
464
805
  capabilities: normalizedCapabilities,
465
- composerActions
806
+ composerActions,
807
+ ...strings === void 0 ? {} : { experimental_strings: strings },
808
+ ...serviceTiers === void 0 ? {} : { experimental_serviceTiers: serviceTiers },
809
+ ...reasoningLevels === void 0 ? {} : { experimental_reasoningLevels: reasoningLevels },
810
+ ...extensionKinds === void 0 ? {} : { experimental_extensionKinds: extensionKinds },
811
+ ...fallbackModels === void 0 ? {} : { experimental_models: Object.freeze({ fallback: fallbackModels }) },
812
+ ...envPassthrough === void 0 ? {} : { experimental_env: Object.freeze({ passthrough: envPassthrough }) },
813
+ ...deriveProviderOptions === void 0 ? {} : { experimental_deriveProviderOptions: deriveProviderOptions }
466
814
  });
467
815
  }
468
816
  function isStandardSchema(value) {
@@ -1402,6 +1750,25 @@ function createFakePluginHostInternal(options, sharedState) {
1402
1750
  const providerRegistrations = [];
1403
1751
  let agentConfigurationProvider = null;
1404
1752
  let instructionProvider = null;
1753
+ function registerProviderDeclaration(declaration) {
1754
+ assertLive();
1755
+ const normalized = validatePluginProviderDeclaration(declaration);
1756
+ if (providerRegistrations.some((existing) => existing.id === normalized.id)) {
1757
+ throw new Error(
1758
+ `Provider "${normalized.id}" is already registered; a plugin cannot shadow an existing provider.`
1759
+ );
1760
+ }
1761
+ providerRegistrations.push(normalized);
1762
+ let disposed2 = false;
1763
+ const dispose = () => {
1764
+ if (disposed2) return;
1765
+ disposed2 = true;
1766
+ const index = providerRegistrations.indexOf(normalized);
1767
+ if (index !== -1) providerRegistrations.splice(index, 1);
1768
+ };
1769
+ disposeHooks.push(dispose);
1770
+ return { dispose };
1771
+ }
1405
1772
  const agents = {
1406
1773
  configure(provider) {
1407
1774
  assertLive();
@@ -1428,23 +1795,7 @@ function createFakePluginHostInternal(options, sharedState) {
1428
1795
  instructionProvider = provider;
1429
1796
  },
1430
1797
  experimental_registerProvider(declaration) {
1431
- assertLive();
1432
- const normalized = validatePluginProviderDeclaration(declaration);
1433
- if (providerRegistrations.some((existing) => existing.id === normalized.id)) {
1434
- throw new Error(
1435
- `Provider "${normalized.id}" is already registered; a plugin cannot shadow an existing provider.`
1436
- );
1437
- }
1438
- providerRegistrations.push(normalized);
1439
- let disposed2 = false;
1440
- const dispose = () => {
1441
- if (disposed2) return;
1442
- disposed2 = true;
1443
- const index = providerRegistrations.indexOf(normalized);
1444
- if (index !== -1) providerRegistrations.splice(index, 1);
1445
- };
1446
- disposeHooks.push(dispose);
1447
- return { dispose };
1798
+ return registerProviderDeclaration(declaration);
1448
1799
  },
1449
1800
  registerTool(tool) {
1450
1801
  assertLive();
@@ -1815,6 +2166,11 @@ function createFakePluginHostInternal(options, sharedState) {
1815
2166
  handlers.push(handler);
1816
2167
  }
1817
2168
  };
2169
+ const providers = {
2170
+ register(declaration) {
2171
+ return registerProviderDeclaration(declaration);
2172
+ }
2173
+ };
1818
2174
  const bb = {
1819
2175
  pluginId,
1820
2176
  log,
@@ -1826,6 +2182,7 @@ function createFakePluginHostInternal(options, sharedState) {
1826
2182
  background,
1827
2183
  cli,
1828
2184
  agents,
2185
+ providers,
1829
2186
  ui,
1830
2187
  events,
1831
2188
  status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-bb/plugin-sdk",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "homepage": "https://github.com/get-bb/bb#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/get-bb/bb/issues"
@@ -32,6 +32,12 @@
32
32
  "import": "./dist/provider-bridge.js",
33
33
  "default": "./dist/provider-bridge.js"
34
34
  },
35
+ "./provider-bridge/testing": {
36
+ "source": "./src/provider-bridge-testing.ts",
37
+ "types": "./bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts",
38
+ "import": "./dist/provider-bridge-testing.js",
39
+ "default": "./dist/provider-bridge-testing.js"
40
+ },
35
41
  "./app": {
36
42
  "source": "./src/app.ts",
37
43
  "types": "./bundled-types/bb-plugin-sdk-app.d.ts",