@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.
package/dist/app.js CHANGED
@@ -22,6 +22,7 @@ var experimental_useSidebarThreads = runtime.experimental_useSidebarThreads;
22
22
  var experimental_useSidebarThreadActions = runtime.experimental_useSidebarThreadActions;
23
23
  var experimental_useSidebarThreadPullRequest = runtime.experimental_useSidebarThreadPullRequest;
24
24
  var experimental_useSidebarThreadSplit = runtime.experimental_useSidebarThreadSplit;
25
+ var experimental_useProviders = runtime.experimental_useProviders;
25
26
  export {
26
27
  Markdown,
27
28
  ThreadChat,
@@ -33,6 +34,7 @@ export {
33
34
  experimental_UrlLink,
34
35
  experimental_useAppPanel,
35
36
  experimental_useFixedTabTarget,
37
+ experimental_useProviders,
36
38
  experimental_useSidebarThreadActions,
37
39
  experimental_useSidebarThreadPullRequest,
38
40
  experimental_useSidebarThreadSplit,
@@ -256,7 +256,7 @@ function validateProviderLiteralArray(args) {
256
256
  }
257
257
  return Object.freeze(normalized);
258
258
  }
259
- function normalizeProviderBridgeOptions(providerId, value) {
259
+ function normalizeProviderBridgeOptions(providerId, value, label = "experimental_bridgeOptions") {
260
260
  const active = /* @__PURE__ */ new Set();
261
261
  function visit(current, path) {
262
262
  if (current === null || typeof current === "string" || typeof current === "boolean") {
@@ -265,14 +265,14 @@ function normalizeProviderBridgeOptions(providerId, value) {
265
265
  if (typeof current === "number") {
266
266
  if (!Number.isFinite(current)) {
267
267
  throw new Error(
268
- `provider "${providerId}" experimental_bridgeOptions${path} must be finite JSON`
268
+ `provider "${providerId}" ${label}${path} must be finite JSON`
269
269
  );
270
270
  }
271
271
  return current;
272
272
  }
273
273
  if (typeof current !== "object") {
274
274
  throw new Error(
275
- `provider "${providerId}" experimental_bridgeOptions${path} must be JSON`
275
+ `provider "${providerId}" ${label}${path} must be JSON`
276
276
  );
277
277
  }
278
278
  if (active.has(current)) {
@@ -292,7 +292,7 @@ function normalizeProviderBridgeOptions(providerId, value) {
292
292
  const prototype = Object.getPrototypeOf(current);
293
293
  if (prototype !== Object.prototype && prototype !== null) {
294
294
  throw new Error(
295
- `provider "${providerId}" experimental_bridgeOptions${path} must contain only plain JSON objects`
295
+ `provider "${providerId}" ${label}${path} must contain only plain JSON objects`
296
296
  );
297
297
  }
298
298
  const normalized2 = Object.fromEntries(
@@ -310,16 +310,329 @@ function normalizeProviderBridgeOptions(providerId, value) {
310
310
  const normalized = visit(value, "");
311
311
  if (normalized === null || Array.isArray(normalized) || typeof normalized !== "object") {
312
312
  throw new Error(
313
- `provider "${providerId}" experimental_bridgeOptions must be an object`
313
+ `provider "${providerId}" ${label} must be an object`
314
314
  );
315
315
  }
316
316
  if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES) {
317
317
  throw new Error(
318
- `provider "${providerId}" experimental_bridgeOptions exceeds ${PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES} bytes`
318
+ `provider "${providerId}" ${label} exceeds ${PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES} bytes`
319
319
  );
320
320
  }
321
321
  return normalized;
322
322
  }
323
+ var PROVIDER_STRING_MAX_CHARS = 512;
324
+ var PROVIDER_EXTENSION_KIND_NAME_PATTERN = /^[a-z0-9-]+$/u;
325
+ var PROVIDER_EXTENSION_KINDS_MAX = 32;
326
+ function requireNonBlankString(args) {
327
+ const { providerId, field, value } = args;
328
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > PROVIDER_STRING_MAX_CHARS) {
329
+ throw new Error(
330
+ `provider "${providerId}" ${field} must be a non-blank string of at most ${PROVIDER_STRING_MAX_CHARS} characters`
331
+ );
332
+ }
333
+ return value;
334
+ }
335
+ function validateProviderStrings(providerId, value) {
336
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
337
+ throw new Error(
338
+ `provider "${providerId}" experimental_strings must be an object`
339
+ );
340
+ }
341
+ const record = Object.fromEntries(
342
+ Object.entries(value)
343
+ );
344
+ const required = (field) => requireNonBlankString({
345
+ providerId,
346
+ field: `experimental_strings.${field}`,
347
+ value: record[field]
348
+ });
349
+ const optional = (field) => record[field] === void 0 ? void 0 : requireNonBlankString({
350
+ providerId,
351
+ field: `experimental_strings.${field}`,
352
+ value: record[field]
353
+ });
354
+ let iconTint;
355
+ if (record.iconTint !== void 0) {
356
+ const tint = record.iconTint;
357
+ if (typeof tint !== "object" || tint === null || Array.isArray(tint)) {
358
+ throw new Error(
359
+ `provider "${providerId}" experimental_strings.iconTint must be { light, dark }`
360
+ );
361
+ }
362
+ const tintRecord = Object.fromEntries(
363
+ Object.entries(tint)
364
+ );
365
+ iconTint = Object.freeze({
366
+ light: requireNonBlankString({
367
+ providerId,
368
+ field: "experimental_strings.iconTint.light",
369
+ value: tintRecord.light
370
+ }),
371
+ dark: requireNonBlankString({
372
+ providerId,
373
+ field: "experimental_strings.iconTint.dark",
374
+ value: tintRecord.dark
375
+ })
376
+ });
377
+ }
378
+ const brandPrefix = optional("brandPrefix");
379
+ const planModeCopy = optional("planModeCopy");
380
+ return Object.freeze({
381
+ signInHint: required("signInHint"),
382
+ expiredHint: required("expiredHint"),
383
+ installUrl: required("installUrl"),
384
+ ...brandPrefix === void 0 ? {} : { brandPrefix },
385
+ ...planModeCopy === void 0 ? {} : { planModeCopy },
386
+ ...iconTint === void 0 ? {} : { iconTint }
387
+ });
388
+ }
389
+ function validateProviderOptionDescriptors(args) {
390
+ const { providerId, field, value } = args;
391
+ if (!Array.isArray(value) || value.length === 0) {
392
+ throw new Error(
393
+ `provider "${providerId}" ${field} must be a non-empty array of { id, label, description? }`
394
+ );
395
+ }
396
+ const seen = /* @__PURE__ */ new Set();
397
+ const normalized = value.map(
398
+ (entry, index) => {
399
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
400
+ throw new Error(
401
+ `provider "${providerId}" ${field}[${index}] must be { id, label, description? }`
402
+ );
403
+ }
404
+ const record = Object.fromEntries(
405
+ Object.entries(entry)
406
+ );
407
+ const id = requireNonBlankString({
408
+ providerId,
409
+ field: `${field}[${index}].id`,
410
+ value: record.id
411
+ });
412
+ if (seen.has(id)) {
413
+ throw new Error(
414
+ `provider "${providerId}" ${field} id ${JSON.stringify(id)} is duplicated`
415
+ );
416
+ }
417
+ seen.add(id);
418
+ const label = requireNonBlankString({
419
+ providerId,
420
+ field: `${field}[${index}].label`,
421
+ value: record.label
422
+ });
423
+ const description = record.description === void 0 ? void 0 : requireNonBlankString({
424
+ providerId,
425
+ field: `${field}[${index}].description`,
426
+ value: record.description
427
+ });
428
+ return Object.freeze({
429
+ id,
430
+ label,
431
+ ...description === void 0 ? {} : { description }
432
+ });
433
+ }
434
+ );
435
+ return Object.freeze(normalized);
436
+ }
437
+ function validateProviderExtensionKinds(providerId, value) {
438
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
439
+ throw new Error(
440
+ `provider "${providerId}" experimental_extensionKinds must be an object keyed by kind name`
441
+ );
442
+ }
443
+ const entries = Object.entries(value);
444
+ if (entries.length > PROVIDER_EXTENSION_KINDS_MAX) {
445
+ throw new Error(
446
+ `provider "${providerId}" experimental_extensionKinds declares more than ${PROVIDER_EXTENSION_KINDS_MAX} kinds`
447
+ );
448
+ }
449
+ const normalized = {};
450
+ for (const [name, declaration] of entries) {
451
+ if (!PROVIDER_EXTENSION_KIND_NAME_PATTERN.test(name)) {
452
+ throw new Error(
453
+ `provider "${providerId}" experimental_extensionKinds name ${JSON.stringify(name)} must match ${PROVIDER_EXTENSION_KIND_NAME_PATTERN}`
454
+ );
455
+ }
456
+ if (typeof declaration !== "object" || declaration === null || Array.isArray(declaration)) {
457
+ throw new Error(
458
+ `provider "${providerId}" experimental_extensionKinds.${name} must be { item?, state? }`
459
+ );
460
+ }
461
+ const item = Reflect.get(declaration, "item");
462
+ const state = Reflect.get(declaration, "state");
463
+ if (item === void 0 && state === void 0) {
464
+ throw new Error(
465
+ `provider "${providerId}" experimental_extensionKinds.${name} must declare an item schema, a state schema, or both`
466
+ );
467
+ }
468
+ if (item !== void 0 && !isStandardSchema(item)) {
469
+ throw new Error(
470
+ `provider "${providerId}" experimental_extensionKinds.${name}.item must be a Standard Schema v1 validator`
471
+ );
472
+ }
473
+ if (state !== void 0 && !isStandardSchema(state)) {
474
+ throw new Error(
475
+ `provider "${providerId}" experimental_extensionKinds.${name}.state must be a Standard Schema v1 validator`
476
+ );
477
+ }
478
+ normalized[name] = Object.freeze({
479
+ ...item === void 0 ? {} : { item },
480
+ ...state === void 0 ? {} : { state }
481
+ });
482
+ }
483
+ return Object.freeze(normalized);
484
+ }
485
+ var PROVIDER_FALLBACK_MODELS_MAX = 64;
486
+ var PROVIDER_ENV_PASSTHROUGH_MAX = 32;
487
+ var PROVIDER_ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/u;
488
+ function validateProviderEnvPassthrough(providerId, value) {
489
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
490
+ throw new Error(
491
+ `provider "${providerId}" experimental_env must be { passthrough: [...] }`
492
+ );
493
+ }
494
+ const passthrough = Reflect.get(value, "passthrough");
495
+ if (!Array.isArray(passthrough)) {
496
+ throw new Error(
497
+ `provider "${providerId}" experimental_env.passthrough must be an array of variable names`
498
+ );
499
+ }
500
+ if (passthrough.length > PROVIDER_ENV_PASSTHROUGH_MAX) {
501
+ throw new Error(
502
+ `provider "${providerId}" experimental_env.passthrough names more than ${PROVIDER_ENV_PASSTHROUGH_MAX} variables`
503
+ );
504
+ }
505
+ const seen = /* @__PURE__ */ new Set();
506
+ for (const name of passthrough) {
507
+ if (typeof name !== "string" || !PROVIDER_ENV_NAME_PATTERN.test(name)) {
508
+ throw new Error(
509
+ `provider "${providerId}" experimental_env.passthrough entries must match ${PROVIDER_ENV_NAME_PATTERN}`
510
+ );
511
+ }
512
+ if (seen.has(name)) {
513
+ throw new Error(
514
+ `provider "${providerId}" experimental_env.passthrough repeats ${JSON.stringify(name)}`
515
+ );
516
+ }
517
+ seen.add(name);
518
+ }
519
+ return Object.freeze([...seen]);
520
+ }
521
+ function validateProviderFallbackModels(providerId, value) {
522
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
523
+ throw new Error(
524
+ `provider "${providerId}" experimental_models must be { fallback: [...] }`
525
+ );
526
+ }
527
+ const fallback = Reflect.get(value, "fallback");
528
+ if (!Array.isArray(fallback)) {
529
+ throw new Error(
530
+ `provider "${providerId}" experimental_models.fallback must be an array`
531
+ );
532
+ }
533
+ if (fallback.length > PROVIDER_FALLBACK_MODELS_MAX) {
534
+ throw new Error(
535
+ `provider "${providerId}" experimental_models.fallback lists more than ${PROVIDER_FALLBACK_MODELS_MAX} models`
536
+ );
537
+ }
538
+ const seen = /* @__PURE__ */ new Set();
539
+ let defaults = 0;
540
+ const normalized = fallback.map((entry, index) => {
541
+ const field = `experimental_models.fallback[${index}]`;
542
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
543
+ throw new Error(`provider "${providerId}" ${field} must be an object`);
544
+ }
545
+ const record = Object.fromEntries(
546
+ Object.entries(entry)
547
+ );
548
+ const id = requireNonBlankString({
549
+ providerId,
550
+ field: `${field}.id`,
551
+ value: record.id
552
+ });
553
+ if (seen.has(id)) {
554
+ throw new Error(
555
+ `provider "${providerId}" experimental_models.fallback id ${JSON.stringify(id)} is duplicated`
556
+ );
557
+ }
558
+ seen.add(id);
559
+ const displayName = requireNonBlankString({
560
+ providerId,
561
+ field: `${field}.displayName`,
562
+ value: record.displayName
563
+ });
564
+ const description = requireNonBlankString({
565
+ providerId,
566
+ field: `${field}.description`,
567
+ value: record.description
568
+ });
569
+ const efforts = record.supportedReasoningEfforts;
570
+ if (!Array.isArray(efforts) || efforts.length === 0) {
571
+ throw new Error(
572
+ `provider "${providerId}" ${field}.supportedReasoningEfforts must be a non-empty array`
573
+ );
574
+ }
575
+ const levels = /* @__PURE__ */ new Set();
576
+ const supportedReasoningEfforts = efforts.map(
577
+ (effort, effortIndex) => {
578
+ if (typeof effort !== "object" || effort === null || Array.isArray(effort)) {
579
+ throw new Error(
580
+ `provider "${providerId}" ${field}.supportedReasoningEfforts[${effortIndex}] must be { reasoningEffort, description }`
581
+ );
582
+ }
583
+ const reasoningEffort = Reflect.get(effort, "reasoningEffort");
584
+ if (typeof reasoningEffort !== "string" || !PLUGIN_PROVIDER_REASONING_LEVEL_VALUES.includes(
585
+ reasoningEffort
586
+ )) {
587
+ throw new Error(
588
+ `provider "${providerId}" ${field}.supportedReasoningEfforts[${effortIndex}].reasoningEffort must be one of ${PLUGIN_PROVIDER_REASONING_LEVEL_VALUES.join(", ")}`
589
+ );
590
+ }
591
+ const level = reasoningEffort;
592
+ if (levels.has(level)) {
593
+ throw new Error(
594
+ `provider "${providerId}" ${field}.supportedReasoningEfforts repeats ${JSON.stringify(level)}`
595
+ );
596
+ }
597
+ levels.add(level);
598
+ return Object.freeze({
599
+ reasoningEffort: level,
600
+ description: requireNonBlankString({
601
+ providerId,
602
+ field: `${field}.supportedReasoningEfforts[${effortIndex}].description`,
603
+ value: Reflect.get(effort, "description")
604
+ })
605
+ });
606
+ }
607
+ );
608
+ const defaultReasoningEffort = record.defaultReasoningEffort;
609
+ if (typeof defaultReasoningEffort !== "string" || !levels.has(defaultReasoningEffort)) {
610
+ throw new Error(
611
+ `provider "${providerId}" ${field}.defaultReasoningEffort must be one of its supportedReasoningEfforts`
612
+ );
613
+ }
614
+ if (typeof record.isDefault !== "boolean") {
615
+ throw new Error(
616
+ `provider "${providerId}" ${field}.isDefault must be a boolean`
617
+ );
618
+ }
619
+ if (record.isDefault) defaults += 1;
620
+ return Object.freeze({
621
+ id,
622
+ displayName,
623
+ description,
624
+ supportedReasoningEfforts: Object.freeze(supportedReasoningEfforts),
625
+ defaultReasoningEffort,
626
+ isDefault: record.isDefault
627
+ });
628
+ });
629
+ if (normalized.length > 0 && defaults !== 1) {
630
+ throw new Error(
631
+ `provider "${providerId}" experimental_models.fallback must mark exactly one model isDefault (found ${defaults})`
632
+ );
633
+ }
634
+ return Object.freeze(normalized);
635
+ }
323
636
  function validatePluginProviderDeclaration(declaration) {
324
637
  if (typeof declaration !== "object" || declaration === null) {
325
638
  throw new Error("provider declaration must be an object");
@@ -330,6 +643,12 @@ function validatePluginProviderDeclaration(declaration) {
330
643
  `invalid provider id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, and "-", starting with a letter or digit`
331
644
  );
332
645
  }
646
+ const family = declaration.experimental_family;
647
+ if (family !== void 0 && (typeof family !== "string" || !PROVIDER_ID_PATTERN.test(family))) {
648
+ throw new Error(
649
+ `provider "${id}" experimental_family must use the provider id grammar (2-64 lowercase letters, digits, and "-")`
650
+ );
651
+ }
333
652
  const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
334
653
  if (displayName.length === 0 || displayName.length > PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS) {
335
654
  throw new Error(
@@ -380,8 +699,7 @@ function validatePluginProviderDeclaration(declaration) {
380
699
  "supportsNativeUserQuestion",
381
700
  "supportsManualCompaction",
382
701
  "supportsThreadArchive",
383
- "supportsThreadRename",
384
- "supportsWorkflows"
702
+ "supportsThreadRename"
385
703
  ];
386
704
  for (const field of booleanCapabilityFields) {
387
705
  if (typeof capabilities[field] !== "boolean") {
@@ -405,7 +723,6 @@ function validatePluginProviderDeclaration(declaration) {
405
723
  supportsManualCompaction: capabilities.supportsManualCompaction,
406
724
  supportsThreadArchive: capabilities.supportsThreadArchive,
407
725
  supportsThreadRename: capabilities.supportsThreadRename,
408
- supportsWorkflows: capabilities.supportsWorkflows,
409
726
  permissionModes: validateProviderLiteralArray({
410
727
  providerId: id,
411
728
  field: "capabilities.permissionModes",
@@ -443,16 +760,57 @@ function validatePluginProviderDeclaration(declaration) {
443
760
  `provider "${id}" experimental_visibility "installed" requires experimental_providerHealth`
444
761
  );
445
762
  }
763
+ const strings = declaration.experimental_strings === void 0 ? void 0 : validateProviderStrings(id, declaration.experimental_strings);
764
+ const serviceTiers = declaration.experimental_serviceTiers === void 0 ? void 0 : validateProviderOptionDescriptors({
765
+ providerId: id,
766
+ field: "experimental_serviceTiers",
767
+ value: declaration.experimental_serviceTiers
768
+ });
769
+ const reasoningLevels = declaration.experimental_reasoningLevels === void 0 ? void 0 : validateProviderOptionDescriptors({
770
+ providerId: id,
771
+ field: "experimental_reasoningLevels",
772
+ value: declaration.experimental_reasoningLevels
773
+ });
774
+ const extensionKinds = declaration.experimental_extensionKinds === void 0 ? void 0 : validateProviderExtensionKinds(
775
+ id,
776
+ declaration.experimental_extensionKinds
777
+ );
778
+ const fallbackModels = declaration.experimental_models === void 0 ? void 0 : validateProviderFallbackModels(id, declaration.experimental_models);
779
+ const envPassthrough = declaration.experimental_env === void 0 ? void 0 : validateProviderEnvPassthrough(id, declaration.experimental_env);
780
+ const deriveProviderOptions = declaration.experimental_deriveProviderOptions;
781
+ if (deriveProviderOptions !== void 0 && typeof deriveProviderOptions !== "function") {
782
+ throw new Error(
783
+ `provider "${id}" experimental_deriveProviderOptions must be a function (context) => providerOptions`
784
+ );
785
+ }
446
786
  return Object.freeze({
447
787
  id,
448
788
  displayName,
789
+ ...family === void 0 ? {} : { experimental_family: family },
449
790
  ...icon === void 0 ? {} : { icon },
450
791
  ...bridgeOptions === void 0 ? {} : { experimental_bridgeOptions: bridgeOptions },
451
792
  experimental_visibility: visibility,
452
793
  capabilities: normalizedCapabilities,
453
- composerActions
794
+ composerActions,
795
+ ...strings === void 0 ? {} : { experimental_strings: strings },
796
+ ...serviceTiers === void 0 ? {} : { experimental_serviceTiers: serviceTiers },
797
+ ...reasoningLevels === void 0 ? {} : { experimental_reasoningLevels: reasoningLevels },
798
+ ...extensionKinds === void 0 ? {} : { experimental_extensionKinds: extensionKinds },
799
+ ...fallbackModels === void 0 ? {} : { experimental_models: Object.freeze({ fallback: fallbackModels }) },
800
+ ...envPassthrough === void 0 ? {} : { experimental_env: Object.freeze({ passthrough: envPassthrough }) },
801
+ ...deriveProviderOptions === void 0 ? {} : { experimental_deriveProviderOptions: deriveProviderOptions }
454
802
  });
455
803
  }
804
+ function deriveValidatedProviderOptions(args) {
805
+ const hook = args.declaration.experimental_deriveProviderOptions;
806
+ if (hook === void 0) return Object.freeze({});
807
+ const result = hook(args.context);
808
+ return normalizeProviderBridgeOptions(
809
+ args.declaration.id,
810
+ result,
811
+ "experimental_deriveProviderOptions result"
812
+ );
813
+ }
456
814
  function isStandardSchema(value) {
457
815
  if (typeof value !== "object" || value === null) return false;
458
816
  const standard = Reflect.get(value, "~standard");
@@ -723,6 +1081,7 @@ export {
723
1081
  SETTING_KEY_PATTERN,
724
1082
  adoptHttpRouteResponse,
725
1083
  assertNoRecursiveJsonSchemaReferences,
1084
+ deriveValidatedProviderOptions,
726
1085
  enforcePluginCliOutputLimit,
727
1086
  isPluginMentionTrigger,
728
1087
  isStandardSchema,