@get-bb/plugin-sdk 0.4.14 → 0.4.16

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.
@@ -5,6 +5,8 @@
5
5
  // Confused by the API, or need a symbol that isn't here? Clone the BB repo
6
6
  // and read the real source: https://github.com/get-bb/bb
7
7
 
8
+ import { z } from 'zod';
9
+
8
10
  /**
9
11
  * Core `bb` CLI top-level command names (plus commander's built-in help).
10
12
  * Plugin CLI commands may not shadow these. Maintained by hand and checked
@@ -16,6 +18,58 @@
16
18
  */
17
19
  declare const RESERVED_BB_CLI_COMMANDS: readonly string[];
18
20
 
21
+ /** Input-form entry: a path, or a path with options. */
22
+ declare const providerNativeRootInputSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
23
+ ancestors: z.ZodOptional<z.ZodBoolean>;
24
+ namePrefix: z.ZodOptional<z.ZodString>;
25
+ path: z.ZodString;
26
+ recursive: z.ZodOptional<z.ZodBoolean>;
27
+ skipIfManifest: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strict>]>;
29
+ /**
30
+ * One provider-native root as a plugin declares it: a path, or a path with
31
+ * options. `recursive`: the agent scans nested skill directories. `ancestors`
32
+ * (project roots only): scan the same relative directory in every ancestor of
33
+ * the workspace up to the repository root. `namePrefix`: prepended to every
34
+ * name under the root, a vendor plugin's `plugin-name:`; a prefixed root is
35
+ * listed as a plugin root. `skipIfManifest`: a vendor-plugin marker file to
36
+ * skip by.
37
+ */
38
+ type ProviderNativeRootInput = z.infer<typeof providerNativeRootInputSchema>;
39
+ /**
40
+ * Normalized roots: relative to the host home (`user`) or to the workspace
41
+ * (`project`). The daemon parses this off the wire; the server produces it
42
+ * from a declaration.
43
+ */
44
+ declare const providerNativeRootsSchema: z.ZodObject<{
45
+ project: z.ZodArray<z.ZodObject<{
46
+ ancestors: z.ZodBoolean;
47
+ namePrefix: z.ZodString;
48
+ path: z.ZodString;
49
+ recursive: z.ZodBoolean;
50
+ skipIfManifest: z.ZodOptional<z.ZodString>;
51
+ }, z.core.$strict>>;
52
+ user: z.ZodArray<z.ZodObject<{
53
+ ancestors: z.ZodBoolean;
54
+ namePrefix: z.ZodString;
55
+ path: z.ZodString;
56
+ recursive: z.ZodBoolean;
57
+ skipIfManifest: z.ZodOptional<z.ZodString>;
58
+ }, z.core.$strict>>;
59
+ }, z.core.$strict>;
60
+ type ProviderNativeRoots = z.infer<typeof providerNativeRootsSchema>;
61
+ /**
62
+ * Provider-native roots as a plugin's frozen declaration holds them: relative
63
+ * to the target host's home (`user`) or to the workspace (`project`). Paths
64
+ * are relative without dot segments, unique per side, at most 32 per side. A
65
+ * root only one host can name — a moved config directory, a settings entry —
66
+ * is the resolver's answer (`resolveNativeRoots`), never a declared root.
67
+ */
68
+ interface ProviderNativeRootsInputLike {
69
+ readonly user?: readonly ProviderNativeRootInput[];
70
+ readonly project?: readonly ProviderNativeRootInput[];
71
+ }
72
+
19
73
  /**
20
74
  * How completely a provider can clone one of its sessions — the single
21
75
  * vocabulary shared by the provider declaration
@@ -89,6 +143,11 @@ type PluginSettingDescriptor = {
89
143
  description?: string;
90
144
  /** Stored in a 0600 file under <dataDir>/plugins/<id>/secrets/, never in the db or sent to the frontend. */
91
145
  secret?: true;
146
+ /**
147
+ * Render as a multi-line text field; for JSON or lists. Secrets cannot
148
+ * be multi-line.
149
+ */
150
+ experimental_multiline?: boolean;
92
151
  default?: string;
93
152
  } | {
94
153
  type: "boolean";
@@ -124,6 +183,45 @@ interface PluginCliExecutionResult {
124
183
  stderr: string;
125
184
  error?: PluginCliOutputLimitError;
126
185
  }
186
+ /**
187
+ * The row title of a plugin tool call while it is pending and once it
188
+ * settled. Each label is capped at 80 characters and rendered as plain text.
189
+ */
190
+ interface PluginAgentToolLabels {
191
+ /** Label shown while the tool call is pending. */
192
+ pending: string;
193
+ /** Label shown after the tool call completes successfully. */
194
+ completed: string;
195
+ }
196
+ /**
197
+ * How calls to a native plugin tool read as a timeline row (grammar v3). Every
198
+ * field is optional at registration: the server fills what the plugin leaves
199
+ * out (a generic `Running <name>` / `Ran <name>` label; the plugin's branding
200
+ * glyph, then `Toolbox`) and hands one complete presentation to the provider
201
+ * bridge with the tool definition.
202
+ */
203
+ interface PluginAgentToolPresentation {
204
+ /** Row title while the call is pending and once it settled. */
205
+ label?: PluginAgentToolLabels;
206
+ /**
207
+ * A named host glyph (`{ glyph: "Workflow" }`), or one of this plugin's
208
+ * own declared icons by its namespaced glyph (`{ glyph: "<pluginId>/<name>" }`,
209
+ * an entry of the manifest's `bb.branding.experimental_icons` map). A
210
+ * namespaced glyph that names another plugin or an undeclared name rejects
211
+ * the tool registration.
212
+ */
213
+ icon?: {
214
+ glyph: string;
215
+ };
216
+ /** Low-value rows clients collapse by default (a question a dedicated
217
+ * interaction row already shows, a bookkeeping call). */
218
+ suppress?: boolean;
219
+ /** Accent colour per theme; omitted rows use the neutral row tint. */
220
+ tint?: {
221
+ light: string;
222
+ dark: string;
223
+ };
224
+ }
127
225
  /**
128
226
  * Permission modes a provider can run a session in — BB's own permission
129
227
  * vocabulary, ordered least ("accept-edits") to most ("full") privileged.
@@ -156,17 +254,6 @@ type PluginProviderComposerAction = "goal" | "plan";
156
254
  * starting the bridge first.
157
255
  */
158
256
  interface PluginProviderCapabilities {
159
- /** The provider bridge implements the sessionless `provider/health`
160
- * request. This is host-local readiness, not a network health check. */
161
- experimental_providerHealth: boolean;
162
- /** The provider exposes subscription usage through the sessionless
163
- * `provider/usage` request. False means callers skip the request and usage
164
- * settings omit the provider. A shared bridge that declares true may still
165
- * report usage unavailable for one provider id or return no windows. */
166
- experimental_providerUsage: boolean;
167
- /** The provider bridge implements `provider/installation/status` and
168
- * `provider/installation/run` for host-local installation management. */
169
- experimental_providerInstallation: boolean;
170
257
  /** The provider accepts a fast/priority service-tier choice — shows the
171
258
  * service-tier toggle in the picker. */
172
259
  supportsServiceTier: boolean;
@@ -246,7 +333,7 @@ interface PluginProviderExtensionKindDeclaration {
246
333
  }
247
334
  /**
248
335
  * Per-command context handed to
249
- * {@link PluginProviderDeclaration.experimental_deriveProviderOptions}. The
336
+ * {@link PluginProviderDeclaration.deriveProviderOptions}. The
250
337
  * server builds one for every session and turn command it dispatches on a
251
338
  * thread of this provider.
252
339
  */
@@ -272,6 +359,8 @@ interface PluginProviderOptionsContext {
272
359
  */
273
360
  settings: Readonly<Record<string, PluginSettingValue | undefined>>;
274
361
  }
362
+ /** See {@link PluginProviderDeclaration.models}. */
363
+ type PluginProviderModelCatalogScope = "host" | "workspace";
275
364
  /**
276
365
  * One cold-cache fallback model. The provider's live `model/list` result is
277
366
  * the only real model source; this list stands in only while no probe has
@@ -293,6 +382,24 @@ interface PluginProviderFallbackModel {
293
382
  /** Exactly one entry in the list is the default. */
294
383
  isDefault: boolean;
295
384
  }
385
+ /**
386
+ * Which sessionless maintenance requests a provider bridge implements. The
387
+ * server skips the requests a provider does not declare, and clients omit
388
+ * the matching surfaces, without starting the bridge first.
389
+ */
390
+ interface PluginProviderMaintenance {
391
+ /** `provider/health`: host-local readiness, never a network health check. */
392
+ health?: boolean;
393
+ /** `provider/usage`: subscription usage windows. False means usage settings
394
+ * omit the provider. A shared bridge that declares true may still report
395
+ * usage unavailable for one provider id or return no windows. */
396
+ usage?: boolean;
397
+ /** `provider/installation/status` and `provider/installation/run`:
398
+ * host-local installation management. */
399
+ installation?: boolean;
400
+ }
401
+ /** Provider-native roots as a plugin declares them, one list per side. */
402
+ type PluginProviderNativeRoots = ProviderNativeRootsInputLike;
296
403
  /**
297
404
  * One provider this plugin contributes to BB's provider registry.
298
405
  *
@@ -303,10 +410,14 @@ interface PluginProviderFallbackModel {
303
410
  * reload, like every other plugin surface.
304
411
  *
305
412
  * A declaration owns the provider's static metadata and bridge options. The
306
- * executable implementation is the plugin's own provider bridge, named by
307
- * `bb.providerBridge` in the manifest and built into the artifact BB ships to
308
- * hosts declaring a provider without one is refused, because the picker
309
- * entry would exist and no turn on it could ever run.
413
+ * executable implementation is the plugin's own provider bridge: the
414
+ * `experimental_providerBridge` export of the `bb.host` artifact the manifest
415
+ * names (`PROVIDER_BRIDGE_EXPORT_NAME` in the bridge kit), built into the
416
+ * artifact BB ships to hosts. Declaring a provider in a plugin with no
417
+ * `bb.host` entry is refused, because the picker entry would exist and no
418
+ * turn on it could ever run; a `bb.host` entry whose artifact failed to
419
+ * build still stages the declaration so the provider is listed as
420
+ * unavailable.
310
421
  */
311
422
  interface PluginProviderDeclaration {
312
423
  /** Stable provider id: 2–64 characters of lowercase letters, digits, and
@@ -320,11 +431,15 @@ interface PluginProviderDeclaration {
320
431
  * family — the ACP agents, for example — so clients can group them without
321
432
  * parsing a prefix out of the id. Grouping only: no policy keys on it.
322
433
  */
323
- experimental_family?: string;
434
+ family?: string;
324
435
  /**
325
- * Optional picker icon, in the same grammar as `bb.branding.icon`: either a
326
- * named host glyph (`"Zap"`) or a plugin-relative path starting with `"./"`
327
- * (`"./icons/agent.svg"`). Paths follow the manifest entry-path escape rules
436
+ * Optional picker icon: a named host glyph (`"Zap"`) or a plugin-relative
437
+ * path starting with `"./"` (`"./icons/agent.svg"`) the two forms
438
+ * `bb.branding.icon` takes or, unlike `bb.branding.icon`, one of this
439
+ * plugin's declared icons by its namespaced glyph (`"<pluginId>/<name>"`,
440
+ * an entry of the manifest's `bb.branding.experimental_icons` map; the
441
+ * plugin id must be this plugin's and the name must be declared, else the
442
+ * plugin fails to load). Paths follow the manifest entry-path escape rules
328
443
  * — no leading "/", no ".." segments, no backslashes.
329
444
  */
330
445
  icon?: string;
@@ -341,6 +456,11 @@ interface PluginProviderDeclaration {
341
456
  * bridge reports it installed. Defaults to `"always"`.
342
457
  */
343
458
  experimental_visibility?: "always" | "installed";
459
+ /**
460
+ * The sessionless maintenance requests the provider's bridge implements
461
+ * (docs/provider-plugin-api.md §1). Each defaults to false when omitted.
462
+ */
463
+ maintenance?: PluginProviderMaintenance;
344
464
  /** Pre-session capability facts (see the declaration tests on
345
465
  * {@link PluginProviderCapabilities}). */
346
466
  capabilities: PluginProviderCapabilities;
@@ -348,27 +468,44 @@ interface PluginProviderDeclaration {
348
468
  * (the universal skills typeahead is implicit). */
349
469
  composerActions: readonly PluginProviderComposerAction[];
350
470
  /** Provider copy for core surfaces ({@link PluginProviderStrings}). */
351
- experimental_strings?: PluginProviderStrings;
471
+ strings?: PluginProviderStrings;
352
472
  /** Service tiers this provider accepts, as picker options. Non-empty when
353
473
  * present, unique ids. The coarse `capabilities.supportsServiceTier` stays
354
474
  * until WS2a stabilizes. */
355
- experimental_serviceTiers?: readonly PluginProviderOptionDescriptor[];
475
+ serviceTiers?: readonly PluginProviderOptionDescriptor[];
356
476
  /** Reasoning levels as picker options with labels, beside the coarse
357
477
  * `capabilities.reasoningLevels` ladder (ids only). Non-empty when present,
358
478
  * unique ids. WS2a merges the two. */
359
- experimental_reasoningLevels?: readonly PluginProviderOptionDescriptor[];
479
+ reasoningLevels?: readonly PluginProviderOptionDescriptor[];
360
480
  /** Extension kinds this provider's bridge may emit, keyed by local name
361
481
  * (`[a-z0-9-]+`). The server validates extension payloads against these
362
482
  * schemas at ingest and persists a `provider/unhandled` on a miss. */
363
- experimental_extensionKinds?: Readonly<Record<string, PluginProviderExtensionKindDeclaration>>;
483
+ extensionKinds?: Readonly<Record<string, PluginProviderExtensionKindDeclaration>>;
364
484
  /**
365
485
  * Cold-cache fallback models ({@link PluginProviderFallbackModel}). The
366
486
  * server offers them only while a model probe has not completed or failed
367
487
  * transiently; the live `model/list` result always replaces them. Ids must
368
488
  * be unique and exactly one entry must be the default.
369
489
  */
370
- experimental_models?: {
371
- fallback: readonly PluginProviderFallbackModel[];
490
+ models?: {
491
+ /**
492
+ * Optional: a provider that only declares a catalog `scope` needs no
493
+ * fallback list, and an omitted list reads as no fallbacks at all.
494
+ */
495
+ fallback?: readonly PluginProviderFallbackModel[];
496
+ /**
497
+ * How far one `model/list` answer travels. `"host"` means the catalog is
498
+ * the same everywhere on a machine — the bridge answers from account or
499
+ * agent state and ignores the workspace path — so bb probes once per host
500
+ * and reuses the answer for every environment on it. `"workspace"` (the
501
+ * default) means project configuration can change the answer, so bb
502
+ * probes per workspace and sends the path.
503
+ *
504
+ * Declaring `"host"` wrongly is a stale catalog in a workspace that
505
+ * configured its own models; declaring `"workspace"` wrongly costs a
506
+ * redundant probe. The default is therefore the safe one.
507
+ */
508
+ scope?: PluginProviderModelCatalogScope;
372
509
  };
373
510
  /**
374
511
  * Daemon environment variables this provider's bridge may read. Provider
@@ -377,9 +514,38 @@ interface PluginProviderDeclaration {
377
514
  * and the daemon forwards exactly those variables. Names are
378
515
  * `[A-Z_][A-Z0-9_]*`, at most 32.
379
516
  */
380
- experimental_env?: {
517
+ env?: {
381
518
  passthrough: readonly string[];
382
519
  };
520
+ /**
521
+ * Directories this provider's agent reads its own skills from, relative to
522
+ * the target host's home directory (`user`) or to the workspace
523
+ * (`project`). An agent with skills of its own — an ACP agent pointed at
524
+ * `.cursor/skills`, say — names them here so bb can list them beside its
525
+ * own; core never guesses a provider's skill layout. Paths are relative
526
+ * and may not contain dot segments; each side holds at most 32 roots. One
527
+ * declaration is global, so a directory only one host can name (an agent's
528
+ * settings-configured skills directory, say) is not declared here but
529
+ * resolved on that host (`experimental_resolvesNativeRoots`).
530
+ */
531
+ experimental_nativeSkillRoots?: PluginProviderNativeRoots;
532
+ /**
533
+ * Directories this provider's agent reads its own slash commands from —
534
+ * flat directories of `*.md` prompt files (`.claude/commands`, say) — in
535
+ * the same two-sided shape as `experimental_nativeSkillRoots`. bb offers
536
+ * them in the composer beside the agent's skills.
537
+ */
538
+ experimental_nativeCommandRoots?: PluginProviderNativeRoots;
539
+ /**
540
+ * This plugin's `bb.host` entry implements
541
+ * `experimental_nativeRootsHostContract` (`@get-bb/plugin-sdk/host`): core
542
+ * calls `resolveNativeRoots({ cwd })` on the workspace host when it lists
543
+ * commands or skills, and scans what comes back beside the declared roots.
544
+ * This is where a provider's host-only knowledge goes — a config-moved
545
+ * directory, an installed vendor plugin, a config-file entry — including
546
+ * project-scoped entries, which a global declaration cannot carry.
547
+ */
548
+ experimental_resolvesNativeRoots?: boolean;
383
549
  /**
384
550
  * Derive this provider's opaque per-command options. Called synchronously
385
551
  * by the server for every session and turn command on a thread of this
@@ -392,9 +558,30 @@ interface PluginProviderDeclaration {
392
558
  * a buggy hook cannot silently run a turn with default knobs. Must be fast:
393
559
  * it sits on the turn-submit path.
394
560
  */
395
- experimental_deriveProviderOptions?: (context: PluginProviderOptionsContext) => Readonly<Record<string, JsonValue>>;
561
+ deriveProviderOptions?: (context: PluginProviderOptionsContext) => Readonly<Record<string, JsonValue>>;
396
562
  }
397
563
  type PluginMentionTrigger = "!" | "#" | "$" | "@" | "~";
564
+ /**
565
+ * What a plugin's AI service does. `inference` answers bb's server-side helper
566
+ * completions (thread titles, commit messages: a prompt and a JSON Schema in,
567
+ * a structured value out); `voice` transcribes recorded speech.
568
+ */
569
+ type PluginAiServiceKind = "inference" | "voice";
570
+ /**
571
+ * An AI service a plugin offers from its `bb.host` entry, which implements
572
+ * `experimental_aiServicesHostContract` (`@get-bb/plugin-sdk/ai-services`).
573
+ * The user selects it with `BB_INFERENCE` / `BB_TRANSCRIPTION` set to
574
+ * `<id>/<model>`; core calls the plugin's host entry on the primary host with
575
+ * the `id` on every request, so one entry can serve several services.
576
+ */
577
+ interface PluginAiServiceDeclaration {
578
+ /** The `<serviceId>` segment of the user's setting; stable, lowercase. */
579
+ readonly id: string;
580
+ /** Shown beside the id wherever the setting's options are listed. */
581
+ readonly displayName: string;
582
+ /** Which kinds this service answers; a kind it lacks is not offered. */
583
+ readonly kinds: readonly PluginAiServiceKind[];
584
+ }
398
585
 
399
586
  /**
400
587
  * Built-in dynamic tool names plugins may not shadow. Maintained by hand —
@@ -435,6 +622,61 @@ declare const PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS = 80;
435
622
  declare const PLUGIN_PROVIDER_PERMISSION_MODE_VALUES: readonly ["accept-edits", "auto", "full"];
436
623
  declare const PLUGIN_PROVIDER_REASONING_LEVEL_VALUES: readonly ["none", "low", "medium", "high", "xhigh", "ultracode", "max", "ultra"];
437
624
  declare const PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES: readonly ["plan", "goal"];
625
+ /**
626
+ * AI-service ids the server serves itself: `openai` transcription and the
627
+ * builtin inference providers (pi-ai 0.84). A plugin cannot register one —
628
+ * it would capture the user's prompts and audio. This list is the one source
629
+ * for both the fake host and production (`isServerDirectAiServiceId`);
630
+ * apps/server/test/services/plugins/plugin-ai-services.test.ts pins it to
631
+ * pi-ai's provider registry, so a pi-ai bump must move it in the same change.
632
+ */
633
+ declare const SERVER_DIRECT_AI_SERVICE_IDS: readonly string[];
634
+ /**
635
+ * Validate one `bb.experimental_aiServices.register` declaration the same
636
+ * way in the production host and the fake host. Throws on the first problem;
637
+ * returns a normalized, frozen copy carrying only contract fields.
638
+ */
639
+ declare function validatePluginAiServiceDeclaration(declaration: PluginAiServiceDeclaration): PluginAiServiceDeclaration;
640
+ /**
641
+ * What an AI service binds to, decided at the
642
+ * `bb.experimental_aiServices.register` call: the plugin's built `bb.host`
643
+ * artifact, or — when the plugin declares an entry that failed to build —
644
+ * nothing yet, with the build problem. An unbound service is staged so the
645
+ * factory completes; the load then fails on that problem before the staged
646
+ * registrations flush, so the service never goes live, while a provider the
647
+ * same factory declared can still be retained as unavailable.
648
+ */
649
+ type AiServiceHostBinding<THostArtifact> = {
650
+ readonly artifact: THostArtifact;
651
+ readonly problem: null;
652
+ } | {
653
+ readonly artifact: null;
654
+ readonly problem: string;
655
+ };
656
+ /**
657
+ * The refusals a host makes at `bb.experimental_aiServices.register` before
658
+ * it stages the declaration: a reserved server-direct id, and a plugin with
659
+ * no `bb.host` entry for the service to run on. A plugin whose declared
660
+ * entry failed to build is not refused here: the service is staged unbound,
661
+ * carrying the build problem, so the load fails on that problem — the
662
+ * actionable one — after the factory instead of at this call, and a
663
+ * provider the same factory declares is listed as unavailable rather than
664
+ * lost. Returns what the service binds to. The production host and the fake
665
+ * host both call this, so they refuse identically;
666
+ * apps/server/test/services/plugins/plugin-ai-services.test.ts pins the
667
+ * messages.
668
+ */
669
+ declare function assertAiServiceRegistrable<THostArtifact>(args: {
670
+ id: string;
671
+ /** The plugin's built `bb.host` artifact, or null when it has none. */
672
+ hostArtifact: THostArtifact | null;
673
+ /** Why the artifact is missing when the plugin declared an entry that failed to build. */
674
+ hostArtifactProblem: string | null;
675
+ }): AiServiceHostBinding<THostArtifact>;
676
+ /** The collision a second registration of a live AI-service id raises. */
677
+ declare function aiServiceAlreadyRegisteredMessage(id: string): string;
678
+ /** The collision a second registration of a live provider id raises. */
679
+ declare function providerAlreadyRegisteredMessage(id: string): string;
438
680
  /**
439
681
  * Validate one `bb.providers.register` declaration. Plugin
440
682
  * sources are untyped at runtime, so every field is checked; the production
@@ -442,9 +684,31 @@ declare const PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES: readonly ["plan", "goal"];
442
684
  * declarations identically. Throws a descriptive error on the first problem;
443
685
  * returns a normalized, deeply frozen copy carrying only contract fields.
444
686
  */
445
- declare function validatePluginProviderDeclaration(declaration: PluginProviderDeclaration): PluginProviderDeclaration;
446
687
  /**
447
- * Run a declaration's `experimental_deriveProviderOptions` hook for one
688
+ * A declaration that has been through {@link validatePluginProviderDeclaration}.
689
+ *
690
+ * The validator fills the defaults it owns, so a consumer reads one explicit
691
+ * value rather than re-deciding what an absent field means. Only the fields
692
+ * the validator GUARANTEES are narrowed here; everything else keeps the
693
+ * author-facing shape.
694
+ */
695
+ type NormalizedPluginProviderDeclaration = Omit<PluginProviderDeclaration, "experimental_nativeCommandRoots" | "experimental_nativeSkillRoots" | "experimental_resolvesNativeRoots"> & {
696
+ readonly experimental_nativeSkillRoots?: ProviderNativeRoots;
697
+ readonly experimental_nativeCommandRoots?: ProviderNativeRoots;
698
+ readonly experimental_resolvesNativeRoots: boolean;
699
+ readonly maintenance: {
700
+ readonly health: boolean;
701
+ readonly usage: boolean;
702
+ readonly installation: boolean;
703
+ };
704
+ readonly models: {
705
+ readonly fallback?: readonly PluginProviderFallbackModel[];
706
+ readonly scope: PluginProviderModelCatalogScope;
707
+ };
708
+ };
709
+ declare function validatePluginProviderDeclaration(declaration: PluginProviderDeclaration): NormalizedPluginProviderDeclaration;
710
+ /**
711
+ * Run a declaration's `deriveProviderOptions` hook for one
448
712
  * command and validate its result as a bounded, plain-JSON object — the same
449
713
  * rules as `experimental_bridgeOptions`, because the result rides the same
450
714
  * wire slot. Shared by the real host and the fake so a hook that works in
@@ -452,7 +716,7 @@ declare function validatePluginProviderDeclaration(declaration: PluginProviderDe
452
716
  */
453
717
  declare function deriveValidatedProviderOptions(args: {
454
718
  declaration: PluginProviderDeclaration;
455
- context: Parameters<NonNullable<PluginProviderDeclaration["experimental_deriveProviderOptions"]>>[0];
719
+ context: Parameters<NonNullable<PluginProviderDeclaration["deriveProviderOptions"]>>[0];
456
720
  }): Readonly<Record<string, JsonValue>>;
457
721
  declare function isStandardSchema(value: unknown): value is StandardSchemaV1;
458
722
  declare function readRpcMethodContract(method: string, value: unknown): PluginRpcMethodContract;
@@ -465,6 +729,24 @@ declare function isZodSchemaLike(value: unknown): boolean;
465
729
  * recursive `$ref`, so this is a shared production/fake-host boundary rule.
466
730
  */
467
731
  declare function assertNoRecursiveJsonSchemaReferences(schema: unknown, subject: string): void;
732
+ /**
733
+ * Reject the fields a registration never reads. Renamed fields get the
734
+ * message above; any other `experimental_` field is unknown (the same
735
+ * rule configure() output follows in the plugin service). The production
736
+ * host and the fake host both call this before parsing `presentation`, so
737
+ * a registration built against an older SDK fails a plugin's own unit test
738
+ * with the message bb would give it.
739
+ */
740
+ declare function rejectStaleAgentToolFields(toolName: string, tool: object): void;
741
+ /**
742
+ * The declared shape of `presentation`, copied field by field so
743
+ * a plugin's object cannot smuggle prototypes or extra markup into the
744
+ * persisted row. Labels share the status-label length cap. The production
745
+ * host and the fake host both call this, so a presentation that registers
746
+ * in a plugin unit test registers in bb, and one bb rejects is rejected
747
+ * with the same message.
748
+ */
749
+ declare function parsePluginAgentToolPresentation(toolName: string, value: unknown): PluginAgentToolPresentation | null;
468
750
  /** Compact issue summary from a (possibly foreign-instance) zod error. */
469
751
  declare function summarizeParseIssues(error: unknown): string;
470
752
  declare function enforcePluginCliOutputLimit(result: Omit<PluginCliExecutionResult, "error">, jsonOutput: boolean): PluginCliExecutionResult;
@@ -482,5 +764,30 @@ declare function enforcePluginCliOutputLimit(result: Omit<PluginCliExecutionResu
482
764
  * with cancellation forwarded to the source, so no full-size buffer is made.
483
765
  */
484
766
  declare function adoptHttpRouteResponse(value: unknown): Response;
767
+ /**
768
+ * The one rule for a namespaced glyph (`"<pluginId>/<name>"`) wherever a
769
+ * plugin may reference its own declared icons — a tool presentation at
770
+ * `bb.agents.registerTool`, a provider icon at `bb.providers.register`, and a
771
+ * row presentation at ingest: the plugin id must be the emitting plugin's
772
+ * and the name must be in its `bb.branding.experimental_icons` map. The
773
+ * server and the fake plugin host apply it from here, so a registration the
774
+ * fake accepts is one the server accepts.
775
+ *
776
+ * Returns the reason a glyph is refused, always naming the glyph and the
777
+ * plugin, or null when the glyph is acceptable. A host glyph (no `/`) is
778
+ * never refused here: whether the client can draw it is the client's call.
779
+ */
780
+ declare function undeclaredIconProblem(pluginId: string, declaredIconNames: ReadonlySet<string>, glyph: string): string | null;
781
+ /** `bb.providers.register` refusal for an icon {@link undeclaredIconProblem} rejects. */
782
+ declare function providerIconRefusalMessage(providerId: string, problem: string): string;
783
+ /** `bb.agents.registerTool` refusal for a glyph {@link undeclaredIconProblem} rejects. */
784
+ declare function agentToolIconRefusalMessage(toolName: string, problem: string): string;
785
+ /**
786
+ * `bb.providers.register` refusal for a plugin whose manifest declares no
787
+ * `bb.host` entry: a declaration is metadata, and the bridge it runs on is
788
+ * that entry.
789
+ */
790
+ declare function providerWithoutBridgeMessage(providerId: string): string;
485
791
 
486
- export { AGENT_TOOL_NAME_PATTERN, BACKGROUND_NAME_PATTERN, CLI_COMMAND_NAME_PATTERN, KV_VALUE_MAX_BYTES, MENTION_PROVIDER_ID_PATTERN, PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS, PLUGIN_AGENT_SELECTION_MAX_IDS, PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS, PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS, PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES, PLUGIN_HTTP_METHODS, PLUGIN_MENTION_TRIGGER_VALUES, PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES, PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES, PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS, PLUGIN_PROVIDER_PERMISSION_MODE_VALUES, PLUGIN_PROVIDER_REASONING_LEVEL_VALUES, PROVIDER_ID_PATTERN, RESERVED_AGENT_TOOL_NAMES, RESERVED_BB_CLI_COMMANDS, RPC_METHOD_PATTERN, SETTING_KEY_PATTERN, adoptHttpRouteResponse, assertNoRecursiveJsonSchemaReferences, deriveValidatedProviderOptions, enforcePluginCliOutputLimit, isPluginMentionTrigger, isStandardSchema, isZodSchemaLike, normalizeMentionProviderTriggers, readRpcMethodContract, registerSettingDescriptors, summarizeParseIssues, validatePluginProviderDeclaration, validateSettingsUpdate };
792
+ export { AGENT_TOOL_NAME_PATTERN, BACKGROUND_NAME_PATTERN, CLI_COMMAND_NAME_PATTERN, KV_VALUE_MAX_BYTES, MENTION_PROVIDER_ID_PATTERN, PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS, PLUGIN_AGENT_SELECTION_MAX_IDS, PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS, PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS, PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES, PLUGIN_HTTP_METHODS, PLUGIN_MENTION_TRIGGER_VALUES, PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES, PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES, PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS, PLUGIN_PROVIDER_PERMISSION_MODE_VALUES, PLUGIN_PROVIDER_REASONING_LEVEL_VALUES, PROVIDER_ID_PATTERN, RESERVED_AGENT_TOOL_NAMES, RESERVED_BB_CLI_COMMANDS, RPC_METHOD_PATTERN, SERVER_DIRECT_AI_SERVICE_IDS, SETTING_KEY_PATTERN, adoptHttpRouteResponse, agentToolIconRefusalMessage, aiServiceAlreadyRegisteredMessage, assertAiServiceRegistrable, assertNoRecursiveJsonSchemaReferences, deriveValidatedProviderOptions, enforcePluginCliOutputLimit, isPluginMentionTrigger, isStandardSchema, isZodSchemaLike, normalizeMentionProviderTriggers, parsePluginAgentToolPresentation, providerAlreadyRegisteredMessage, providerIconRefusalMessage, providerWithoutBridgeMessage, readRpcMethodContract, registerSettingDescriptors, rejectStaleAgentToolFields, summarizeParseIssues, undeclaredIconProblem, validatePluginAiServiceDeclaration, validatePluginProviderDeclaration, validateSettingsUpdate };
793
+ export type { AiServiceHostBinding, NormalizedPluginProviderDeclaration };
@@ -5,7 +5,7 @@
5
5
  // Confused by the API, or need a symbol that isn't here? Clone the BB repo
6
6
  // and read the real source: https://github.com/get-bb/bb
7
7
 
8
- import { PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, PluginCommandPaletteActionRegistration, PluginProviderIconRegistration, PluginContentScriptRegistration, PluginAppDefinition } from '@get-bb/plugin-sdk';
8
+ import { PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, PluginCommandPaletteActionRegistration, PluginProviderIconRegistration, PluginTimelineRendererRegistration, PluginContentScriptRegistration, PluginAppDefinition } from '@get-bb/plugin-sdk';
9
9
 
10
10
  /** Validated registrations produced by one plugin app setup execution. */
11
11
  interface CollectedPluginAppRegistrations {
@@ -26,6 +26,7 @@ interface CollectedPluginAppRegistrations {
26
26
  messageActions: PluginMessageActionRegistration[];
27
27
  commandPaletteActions: PluginCommandPaletteActionRegistration[];
28
28
  providerIcons: PluginProviderIconRegistration[];
29
+ timelineRenderers: PluginTimelineRendererRegistration[];
29
30
  contentScripts: PluginContentScriptRegistration[];
30
31
  }
31
32
  /**