@get-bb/plugin-sdk 0.4.9 → 0.4.11
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/README.md +43 -2
- package/bundled-types/bb-plugin-sdk-app.d.ts +253 -28
- package/bundled-types/bb-plugin-sdk-internal-file-navigation-validation.d.ts +42 -0
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +209 -16
- package/bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts +3314 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge.d.ts +1362 -327
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +38 -2
- package/bundled-types/bb-plugin-sdk.d.ts +2259 -526
- package/dist/app.js +10 -0
- package/dist/internal/file-navigation-validation.js +135 -0
- package/dist/internal/host-policy.js +467 -4
- package/dist/internal/plugin-app-collector.js +22 -1
- package/dist/provider-bridge-testing.js +5777 -0
- package/dist/provider-bridge.js +2906 -2136
- package/dist/testing/app.js +334 -2
- package/dist/testing/index.js +481 -21
- package/package.json +13 -1
package/dist/testing/index.js
CHANGED
|
@@ -66,6 +66,7 @@ var PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS = 4096;
|
|
|
66
66
|
var PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES = 128 * 1024;
|
|
67
67
|
var MENTION_PROVIDER_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
68
68
|
var PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,63}$/;
|
|
69
|
+
var PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES = 64 * 1024;
|
|
69
70
|
var SETTING_KEY_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
70
71
|
var settingsBaseFields = {
|
|
71
72
|
label: z2.string().min(1),
|
|
@@ -267,6 +268,383 @@ function validateProviderLiteralArray(args) {
|
|
|
267
268
|
}
|
|
268
269
|
return Object.freeze(normalized);
|
|
269
270
|
}
|
|
271
|
+
function normalizeProviderBridgeOptions(providerId, value, label = "experimental_bridgeOptions") {
|
|
272
|
+
const active = /* @__PURE__ */ new Set();
|
|
273
|
+
function visit(current, path) {
|
|
274
|
+
if (current === null || typeof current === "string" || typeof current === "boolean") {
|
|
275
|
+
return current;
|
|
276
|
+
}
|
|
277
|
+
if (typeof current === "number") {
|
|
278
|
+
if (!Number.isFinite(current)) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`provider "${providerId}" ${label}${path} must be finite JSON`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return current;
|
|
284
|
+
}
|
|
285
|
+
if (typeof current !== "object") {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`provider "${providerId}" ${label}${path} must be JSON`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (active.has(current)) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`provider "${providerId}" experimental_bridgeOptions must not contain cycles`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
active.add(current);
|
|
296
|
+
try {
|
|
297
|
+
if (Array.isArray(current)) {
|
|
298
|
+
const normalized3 = current.map(
|
|
299
|
+
(entry, index) => visit(entry, `${path}[${index}]`)
|
|
300
|
+
);
|
|
301
|
+
Object.freeze(normalized3);
|
|
302
|
+
return normalized3;
|
|
303
|
+
}
|
|
304
|
+
const prototype = Object.getPrototypeOf(current);
|
|
305
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`provider "${providerId}" ${label}${path} must contain only plain JSON objects`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const normalized2 = Object.fromEntries(
|
|
311
|
+
Object.entries(current).map(([key, entry]) => [
|
|
312
|
+
key,
|
|
313
|
+
visit(entry, `${path}.${key}`)
|
|
314
|
+
])
|
|
315
|
+
);
|
|
316
|
+
Object.freeze(normalized2);
|
|
317
|
+
return normalized2;
|
|
318
|
+
} finally {
|
|
319
|
+
active.delete(current);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const normalized = visit(value, "");
|
|
323
|
+
if (normalized === null || Array.isArray(normalized) || typeof normalized !== "object") {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`provider "${providerId}" ${label} must be an object`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`provider "${providerId}" ${label} exceeds ${PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES} bytes`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
return normalized;
|
|
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
|
+
}
|
|
270
648
|
function validatePluginProviderDeclaration(declaration) {
|
|
271
649
|
if (typeof declaration !== "object" || declaration === null) {
|
|
272
650
|
throw new Error("provider declaration must be an object");
|
|
@@ -277,6 +655,12 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
277
655
|
`invalid provider id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, and "-", starting with a letter or digit`
|
|
278
656
|
);
|
|
279
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
|
+
}
|
|
280
664
|
const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
|
|
281
665
|
if (displayName.length === 0 || displayName.length > PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS) {
|
|
282
666
|
throw new Error(
|
|
@@ -304,13 +688,30 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
304
688
|
if (typeof capabilities !== "object" || capabilities === null) {
|
|
305
689
|
throw new Error(`provider "${id}" capabilities must be an object`);
|
|
306
690
|
}
|
|
691
|
+
const experimentalProviderHealth = capabilities.experimental_providerHealth ?? false;
|
|
692
|
+
const experimentalProviderUsage = capabilities.experimental_providerUsage ?? false;
|
|
693
|
+
const experimentalProviderInstallation = capabilities.experimental_providerInstallation ?? false;
|
|
694
|
+
if (typeof experimentalProviderHealth !== "boolean") {
|
|
695
|
+
throw new Error(
|
|
696
|
+
`provider "${id}" capabilities.experimental_providerHealth must be a boolean`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
if (typeof experimentalProviderUsage !== "boolean") {
|
|
700
|
+
throw new Error(
|
|
701
|
+
`provider "${id}" capabilities.experimental_providerUsage must be a boolean`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if (typeof experimentalProviderInstallation !== "boolean") {
|
|
705
|
+
throw new Error(
|
|
706
|
+
`provider "${id}" capabilities.experimental_providerInstallation must be a boolean`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
307
709
|
const booleanCapabilityFields = [
|
|
308
710
|
"supportsServiceTier",
|
|
309
711
|
"supportsNativeUserQuestion",
|
|
310
712
|
"supportsManualCompaction",
|
|
311
713
|
"supportsThreadArchive",
|
|
312
|
-
"supportsThreadRename"
|
|
313
|
-
"supportsWorkflows"
|
|
714
|
+
"supportsThreadRename"
|
|
314
715
|
];
|
|
315
716
|
for (const field of booleanCapabilityFields) {
|
|
316
717
|
if (typeof capabilities[field] !== "boolean") {
|
|
@@ -325,13 +726,15 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
325
726
|
);
|
|
326
727
|
}
|
|
327
728
|
const normalizedCapabilities = Object.freeze({
|
|
729
|
+
experimental_providerHealth: experimentalProviderHealth,
|
|
730
|
+
experimental_providerUsage: experimentalProviderUsage,
|
|
731
|
+
experimental_providerInstallation: experimentalProviderInstallation,
|
|
328
732
|
supportsServiceTier: capabilities.supportsServiceTier,
|
|
329
733
|
supportsNativeUserQuestion: capabilities.supportsNativeUserQuestion,
|
|
330
734
|
fork: capabilities.fork,
|
|
331
735
|
supportsManualCompaction: capabilities.supportsManualCompaction,
|
|
332
736
|
supportsThreadArchive: capabilities.supportsThreadArchive,
|
|
333
737
|
supportsThreadRename: capabilities.supportsThreadRename,
|
|
334
|
-
supportsWorkflows: capabilities.supportsWorkflows,
|
|
335
738
|
permissionModes: validateProviderLiteralArray({
|
|
336
739
|
providerId: id,
|
|
337
740
|
field: "capabilities.permissionModes",
|
|
@@ -354,12 +757,60 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
354
757
|
allowed: PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES,
|
|
355
758
|
requireNonEmpty: false
|
|
356
759
|
});
|
|
760
|
+
const bridgeOptions = declaration.experimental_bridgeOptions === void 0 ? void 0 : normalizeProviderBridgeOptions(
|
|
761
|
+
id,
|
|
762
|
+
declaration.experimental_bridgeOptions
|
|
763
|
+
);
|
|
764
|
+
const visibility = declaration.experimental_visibility ?? "always";
|
|
765
|
+
if (visibility !== "always" && visibility !== "installed") {
|
|
766
|
+
throw new Error(
|
|
767
|
+
`provider "${id}" experimental_visibility must be "always" or "installed"`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
if (visibility === "installed" && !normalizedCapabilities.experimental_providerHealth) {
|
|
771
|
+
throw new Error(
|
|
772
|
+
`provider "${id}" experimental_visibility "installed" requires experimental_providerHealth`
|
|
773
|
+
);
|
|
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
|
+
}
|
|
357
798
|
return Object.freeze({
|
|
358
799
|
id,
|
|
359
800
|
displayName,
|
|
801
|
+
...family === void 0 ? {} : { experimental_family: family },
|
|
360
802
|
...icon === void 0 ? {} : { icon },
|
|
803
|
+
...bridgeOptions === void 0 ? {} : { experimental_bridgeOptions: bridgeOptions },
|
|
804
|
+
experimental_visibility: visibility,
|
|
361
805
|
capabilities: normalizedCapabilities,
|
|
362
|
-
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 }
|
|
363
814
|
});
|
|
364
815
|
}
|
|
365
816
|
function isStandardSchema(value) {
|
|
@@ -1299,6 +1750,25 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1299
1750
|
const providerRegistrations = [];
|
|
1300
1751
|
let agentConfigurationProvider = null;
|
|
1301
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
|
+
}
|
|
1302
1772
|
const agents = {
|
|
1303
1773
|
configure(provider) {
|
|
1304
1774
|
assertLive();
|
|
@@ -1325,23 +1795,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1325
1795
|
instructionProvider = provider;
|
|
1326
1796
|
},
|
|
1327
1797
|
experimental_registerProvider(declaration) {
|
|
1328
|
-
|
|
1329
|
-
const normalized = validatePluginProviderDeclaration(declaration);
|
|
1330
|
-
if (providerRegistrations.some((existing) => existing.id === normalized.id)) {
|
|
1331
|
-
throw new Error(
|
|
1332
|
-
`Provider "${normalized.id}" is already registered; a plugin cannot shadow an existing provider.`
|
|
1333
|
-
);
|
|
1334
|
-
}
|
|
1335
|
-
providerRegistrations.push(normalized);
|
|
1336
|
-
let disposed2 = false;
|
|
1337
|
-
const dispose = () => {
|
|
1338
|
-
if (disposed2) return;
|
|
1339
|
-
disposed2 = true;
|
|
1340
|
-
const index = providerRegistrations.indexOf(normalized);
|
|
1341
|
-
if (index !== -1) providerRegistrations.splice(index, 1);
|
|
1342
|
-
};
|
|
1343
|
-
disposeHooks.push(dispose);
|
|
1344
|
-
return { dispose };
|
|
1798
|
+
return registerProviderDeclaration(declaration);
|
|
1345
1799
|
},
|
|
1346
1800
|
registerTool(tool) {
|
|
1347
1801
|
assertLive();
|
|
@@ -1712,6 +2166,11 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1712
2166
|
handlers.push(handler);
|
|
1713
2167
|
}
|
|
1714
2168
|
};
|
|
2169
|
+
const providers = {
|
|
2170
|
+
register(declaration) {
|
|
2171
|
+
return registerProviderDeclaration(declaration);
|
|
2172
|
+
}
|
|
2173
|
+
};
|
|
1715
2174
|
const bb = {
|
|
1716
2175
|
pluginId,
|
|
1717
2176
|
log,
|
|
@@ -1723,6 +2182,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1723
2182
|
background,
|
|
1724
2183
|
cli,
|
|
1725
2184
|
agents,
|
|
2185
|
+
providers,
|
|
1726
2186
|
ui,
|
|
1727
2187
|
events,
|
|
1728
2188
|
status,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@get-bb/plugin-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
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",
|
|
@@ -56,6 +62,12 @@
|
|
|
56
62
|
"import": "./dist/internal/composer-view.js",
|
|
57
63
|
"default": "./dist/internal/composer-view.js"
|
|
58
64
|
},
|
|
65
|
+
"./internal/file-navigation-validation": {
|
|
66
|
+
"source": "./src/internal/file-navigation-validation.ts",
|
|
67
|
+
"types": "./bundled-types/bb-plugin-sdk-internal-file-navigation-validation.d.ts",
|
|
68
|
+
"import": "./dist/internal/file-navigation-validation.js",
|
|
69
|
+
"default": "./dist/internal/file-navigation-validation.js"
|
|
70
|
+
},
|
|
59
71
|
"./internal/host-policy": {
|
|
60
72
|
"source": "./src/internal/host-policy.ts",
|
|
61
73
|
"types": "./bundled-types/bb-plugin-sdk-internal-host-policy.d.ts",
|