@get-bb/plugin-sdk 0.4.3

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.
@@ -0,0 +1,309 @@
1
+ // Portable type declarations for `@get-bb/plugin-sdk`. Unpublished BB
2
+ // workspace contracts are flattened; public subpaths may reuse the
3
+ // package root without requiring any other @bb/* package.
4
+ //
5
+ // Confused by the API, or need a symbol that isn't here? Clone the BB repo
6
+ // and read the real source: https://github.com/get-bb/bb
7
+
8
+ import { BbPluginApi, PluginSettingValue, PluginSharedPortTunnelIdentity, PluginAgentToolExperimentalStatusLabels, PluginAgentToolContext, PluginAgentToolResult, PluginCliCommandInfo, PluginCliContext, PluginCliResult, PluginHttpAuthMode, PluginHttpHandler, PluginMentionTrigger, PluginMentionSearchContext, PluginMentionItem, JsonValue, PluginCliExecutionResult, PluginThreadEventName, PluginThreadEventPayloads, PluginAgentConfigurationContext, PluginSettingDescriptors, PluginAgentConfiguration, PluginInteractionRequest } from '@get-bb/plugin-sdk';
9
+
10
+ type BbSdk = BbPluginApi["sdk"];
11
+ /**
12
+ * Recordable `bb.sdk` stand-in for {@link createFakePluginHost}. Every call
13
+ * through the fake is recorded (post plugin-attribution defaulting, so
14
+ * assertions see what the server would receive); calls without a stubbed
15
+ * implementation throw with a message naming the exact path to stub.
16
+ */
17
+ /** One recorded `bb.sdk` call. `path` is dot-joined, e.g. "threads.spawn". */
18
+ interface FakeSdkCall {
19
+ path: string;
20
+ args: unknown[];
21
+ }
22
+ /**
23
+ * A stub keeps the real method's parameter types but may return anything —
24
+ * tests usually only build the fields the plugin reads, not the full wire
25
+ * response.
26
+ */
27
+ type LooseStub<F> = F extends (...args: infer A) => unknown ? (...args: A) => unknown : never;
28
+ /**
29
+ * Stub implementations keyed like `BbSdk`: an object per area with a subset
30
+ * of its methods, or a function for the root-level members (`on`).
31
+ */
32
+ type FakeSdkOverrideTree<T> = {
33
+ [K in keyof T]?: T[K] extends (...args: never[]) => unknown ? LooseStub<T[K]> : FakeSdkOverrideTree<T[K]>;
34
+ };
35
+ type FakeSdkOverrides = FakeSdkOverrideTree<BbSdk>;
36
+ interface FakeSdkHarness {
37
+ /** Every `bb.sdk` call in order, including ones whose stub threw. */
38
+ readonly calls: FakeSdkCall[];
39
+ /** Argument lists of the calls to one dot-joined path. */
40
+ callsTo(path: string): unknown[][];
41
+ /** Add or replace one method's implementation after creation. */
42
+ stub(path: string, implementation: (...args: never[]) => unknown): void;
43
+ }
44
+ declare function createFakeSdk(options: {
45
+ pluginId: string;
46
+ overrides?: FakeSdkOverrides;
47
+ }): {
48
+ sdk: BbSdk;
49
+ harness: FakeSdkHarness;
50
+ };
51
+
52
+ /**
53
+ * `createFakePluginHost` — an in-process stand-in for the BB server's plugin
54
+ * runtime (apps/server/src/services/plugins/plugin-api.ts), for unit-testing
55
+ * a plugin's `server.ts` without a server. `bb` satisfies {@link BbPluginApi};
56
+ * `harness` drives and inspects it.
57
+ *
58
+ * Faithful where a plugin can observe it: registration name validation and
59
+ * error messages, the kv 256KB cap, append-only database migrations, settings
60
+ * read/update semantics (including onChange), schema-validated rpc/cli
61
+ * invocation shapes (strict JSON boundaries, exit-code normalization), `threads.spawn`
62
+ * attribution, atomic reload, and dispose order (services aborted, hooks LIFO,
63
+ * database closed, stale handles throw). New tests can keep host inputs,
64
+ * assertions, and shutdown explicit through `harness.behavior`,
65
+ * `harness.inspection`, and `harness.lifecycle`; direct members remain aliases.
66
+ *
67
+ * Deliberately different from the real host:
68
+ * - storage is process-local: kv in a Map, `storage.database()` one shared
69
+ * better-sqlite3 handle in a temp directory (same data across calls, like
70
+ * the host's shared file), secret settings alongside plain values (no files).
71
+ * - `bb.sdk` is always bound (no listen gate) and every unstubbed method
72
+ * throws instead of hitting a server.
73
+ * - http auth modes are recorded but not enforced — signature checks and
74
+ * token handling inside handlers still run.
75
+ * - background services/schedules never run on timers; `harness.runService`
76
+ * and `harness.runSchedule` invoke them deterministically.
77
+ */
78
+ /** Same shape (and name) the real host throws for stale API handles. */
79
+ declare class PluginContextStaleError extends Error {
80
+ constructor(pluginId: string);
81
+ }
82
+ type FakeLogLevel = "debug" | "info" | "warn" | "error";
83
+ interface FakeLogEntry {
84
+ level: FakeLogLevel;
85
+ message: string;
86
+ }
87
+ interface FakeHttpRouteRecord {
88
+ method: string;
89
+ path: string;
90
+ auth: PluginHttpAuthMode;
91
+ handler: PluginHttpHandler;
92
+ }
93
+ interface FakeScheduleRecord {
94
+ name: string;
95
+ cron: string;
96
+ fn: () => void | Promise<void>;
97
+ }
98
+ interface FakeServiceRecord {
99
+ name: string;
100
+ start: (signal: AbortSignal) => void | Promise<void>;
101
+ }
102
+ interface FakeCliRecord {
103
+ name: string;
104
+ summary: string;
105
+ commands: PluginCliCommandInfo[];
106
+ run: (argv: string[], ctx: PluginCliContext) => PluginCliResult | Promise<PluginCliResult>;
107
+ }
108
+ interface FakeAgentToolRecord {
109
+ name: string;
110
+ description: string;
111
+ experimentalStatusLabels: PluginAgentToolExperimentalStatusLabels | null;
112
+ instructions: string | null;
113
+ /** JSON-schema object the host would send providers. */
114
+ inputSchema: unknown;
115
+ parse(input: unknown): {
116
+ ok: true;
117
+ value: unknown;
118
+ } | {
119
+ ok: false;
120
+ error: string;
121
+ };
122
+ execute(params: unknown, ctx: PluginAgentToolContext): PluginAgentToolResult | Promise<PluginAgentToolResult>;
123
+ }
124
+ interface FakeMentionProviderRecord {
125
+ id: string;
126
+ label: string;
127
+ triggers: readonly PluginMentionTrigger[];
128
+ search: (ctx: PluginMentionSearchContext) => PluginMentionItem[] | Promise<PluginMentionItem[]>;
129
+ resolve: (itemId: string) => {
130
+ context: string;
131
+ } | Promise<{
132
+ context: string;
133
+ }>;
134
+ }
135
+ interface FakeRealtimeSignal {
136
+ channel: string;
137
+ /** JSON-round-tripped, like the WS broadcast; `undefined` → `null`. */
138
+ payload: unknown;
139
+ }
140
+ /** Everything the plugin registered, exposed raw for assertions. */
141
+ interface FakePluginRegistrations {
142
+ settingsDescriptors: PluginSettingDescriptors;
143
+ httpRoutes: FakeHttpRouteRecord[];
144
+ rpcMethods: string[];
145
+ services: FakeServiceRecord[];
146
+ schedules: FakeScheduleRecord[];
147
+ cli: FakeCliRecord | null;
148
+ agentTools: FakeAgentToolRecord[];
149
+ /** Provider from bb.agents.configure, or null when none registered. */
150
+ agentConfigurationProvider: ((context: PluginAgentConfigurationContext) => PluginAgentConfiguration) | null;
151
+ /** Provider from contributeInstructions, or null when none registered. */
152
+ instructionProvider: ((ctx: {
153
+ threadId: string;
154
+ projectId: string;
155
+ }) => string | null) | null;
156
+ threadEventHandlers: Record<PluginThreadEventName, number>;
157
+ mentionProviders: FakeMentionProviderRecord[];
158
+ }
159
+ /** Read-only state for assertions after a plugin registers or handles work. */
160
+ interface FakePluginInspectionState {
161
+ readonly pluginId: string;
162
+ /** Every `bb.log` line, in order. */
163
+ readonly logEntries: FakeLogEntry[];
164
+ /** Every `bb.realtime.publish`, payload normalized like the wire. */
165
+ readonly realtimeSignals: FakeRealtimeSignal[];
166
+ /** Every `bb.status.needsConfiguration` message, in order. */
167
+ readonly needsConfigurationMessages: string[];
168
+ /** Recorded `bb.sdk` calls + stub control. */
169
+ readonly sdk: FakeSdkHarness;
170
+ readonly registrations: FakePluginRegistrations;
171
+ readonly sharedPortDeclarations: Array<{
172
+ hostId: string;
173
+ ports: number[];
174
+ }>;
175
+ readonly pendingInteractions: readonly (PluginInteractionRequest & {
176
+ id: string;
177
+ })[];
178
+ }
179
+ /** Deterministic inputs that stand in for behavior normally driven by BB. */
180
+ interface FakePluginBehaviorDrivers {
181
+ submitInteraction(id: string, value: JsonValue): void;
182
+ cancelInteraction(id: string): void;
183
+ /**
184
+ * Apply a settings update the way the host's settings save does:
185
+ * validate against the declared descriptors (`null` unsets), store, and
186
+ * fire `onChange` listeners when effective values changed. Throws on
187
+ * unknown keys or wrong value types.
188
+ */
189
+ setSettings(values: Record<string, PluginSettingValue | null>): Promise<void>;
190
+ /**
191
+ * Invoke a registered rpc method with host semantics: input/output schemas,
192
+ * strict JSON result normalization, and structured failure codes. Rejects
193
+ * with the same message/code/issues the frontend client surfaces.
194
+ */
195
+ callRpc(method: string, input?: unknown): Promise<unknown>;
196
+ /**
197
+ * Invoke the plugin's CLI command with host semantics: the result's
198
+ * exitCode must be a number, stdout/stderr default to "", and a throwing
199
+ * run() becomes `{ exitCode: 1, stderr: "bb <name> failed: …" }`.
200
+ */
201
+ runCli(argv: string[], ctx?: PluginCliContext): Promise<PluginCliExecutionResult>;
202
+ /**
203
+ * Dispatch a request to a registered `bb.http` route (exact method+path
204
+ * match, like the host's V1 router) through a real Hono context. Auth
205
+ * modes are not enforced. A throwing handler yields the host's 500
206
+ * `{ ok: false, error: "plugin route failed: …" }` response.
207
+ */
208
+ fetchHttp(method: string, path: string, init?: RequestInit): Promise<Response>;
209
+ /**
210
+ * Start a registered background service once, deterministically. `done`
211
+ * settles when `start` returns; abort `controller` to signal shutdown.
212
+ * A thrown NeedsConfigurationError (matched by name, like the host) is
213
+ * recorded via needsConfiguration and resolves `done`; other errors
214
+ * reject it.
215
+ */
216
+ runService(name: string): {
217
+ controller: AbortController;
218
+ done: Promise<void>;
219
+ };
220
+ /** Run a registered schedule's function once (no timers, no cron sweep). */
221
+ runSchedule(name: string): Promise<void>;
222
+ /**
223
+ * Deliver a thread lifecycle event to every `bb.events.on` handler. Handlers run
224
+ * sequentially; errors are caught and logged like the host's
225
+ * fire-and-forget dispatch, and returned for assertions.
226
+ */
227
+ emitThreadEvent<E extends PluginThreadEventName>(event: E, payload: PluginThreadEventPayloads[E]): Promise<{
228
+ errors: unknown[];
229
+ }>;
230
+ /**
231
+ * Call a registered agent tool the way a provider tool-call would:
232
+ * arguments go through the tool's parse step (zod-validated for zod
233
+ * registrations; a parse failure throws), then execute. `ctx` fields
234
+ * default to "thread-test"/"project-test" and a fresh signal.
235
+ */
236
+ callAgentTool(name: string, input: unknown, ctx?: Partial<PluginAgentToolContext>): Promise<PluginAgentToolResult>;
237
+ /** Evaluate `bb.agents.configure` with production validation/fail-closed
238
+ * semantics. With no callback, every registered tool/declared test skill is
239
+ * selected. Callback failures are logged and return empty selections. */
240
+ resolveAgentConfiguration(context: PluginAgentConfigurationContext): Promise<{
241
+ tools: FakeAgentToolRecord[];
242
+ skills: string[];
243
+ instructions: string | null;
244
+ }>;
245
+ }
246
+ /** Reload/shutdown controls, kept separate from behavior and inspection. */
247
+ interface FakePluginLifecycleControls {
248
+ /**
249
+ * Load a replacement against the same persisted settings, kv, and database.
250
+ * The current host remains live when the factory throws; on success its
251
+ * services/hooks are disposed and the returned host becomes current.
252
+ */
253
+ reload(factory: (bb: BbPluginApi) => void | Promise<void>): Promise<FakePluginHost>;
254
+ /**
255
+ * Dispose like a host reload/disable: abort services started via
256
+ * runService, run onDispose hooks LIFO (isolated), close database handles,
257
+ * then poison the `bb` handle (further use throws
258
+ * PluginContextStaleError). Idempotent.
259
+ */
260
+ dispose(): Promise<void>;
261
+ }
262
+ /**
263
+ * Complete fake-host harness. Direct members are retained for compatibility;
264
+ * the named views make intent explicit in new tests.
265
+ */
266
+ interface FakePluginHarness extends FakePluginInspectionState, FakePluginBehaviorDrivers, FakePluginLifecycleControls {
267
+ readonly behavior: FakePluginBehaviorDrivers;
268
+ readonly inspection: FakePluginInspectionState;
269
+ readonly lifecycle: FakePluginLifecycleControls;
270
+ }
271
+ interface CreateFakePluginHostOptions {
272
+ /** Defaults to "test-plugin". */
273
+ pluginId?: string;
274
+ /**
275
+ * Value served by `bb.server.loopbackBaseUrl` (always bound here, like
276
+ * `bb.sdk`). Defaults to "http://127.0.0.1:38886".
277
+ */
278
+ loopbackBaseUrl?: string;
279
+ /**
280
+ * Pre-seeded stored settings values (as if saved before this load) —
281
+ * including secret ones, which the fake keeps in memory instead of
282
+ * files. Values with the wrong type for their descriptor fall back to
283
+ * the descriptor default on read, like the host.
284
+ */
285
+ settings?: Record<string, PluginSettingValue>;
286
+ /** Initial `bb.sdk` stubs; extend later via `harness.sdk.stub`. */
287
+ sdk?: FakeSdkOverrides;
288
+ /** Static manifest skill ids available to configure() in this fake host. */
289
+ agentSkillIds?: readonly string[];
290
+ /** Read-only identities returned by bb.hosts.ensureSharedPortTunnel. */
291
+ sharedPortTunnelIdentities?: Record<string, PluginSharedPortTunnelIdentity>;
292
+ }
293
+ interface FakePluginHost {
294
+ bb: BbPluginApi;
295
+ harness: FakePluginHarness;
296
+ }
297
+ declare function createFakePluginHost(options?: CreateFakePluginHostOptions): FakePluginHost;
298
+
299
+ type ThreadResponse = PluginThreadEventPayloads["thread.created"]["thread"];
300
+ /**
301
+ * A complete, deterministic `ThreadResponse` for thread lifecycle event
302
+ * payloads (`harness.emitThreadEvent`). Defaults are the minimal idle
303
+ * thread; override the fields the test cares about. If the contract grows a
304
+ * required field, this builder fails typecheck — update the default here.
305
+ */
306
+ declare function makeThreadResponse(overrides?: Partial<ThreadResponse>): ThreadResponse;
307
+
308
+ export { PluginContextStaleError, createFakePluginHost, createFakeSdk, makeThreadResponse };
309
+ export type { CreateFakePluginHostOptions, FakeAgentToolRecord, FakeCliRecord, FakeHttpRouteRecord, FakeLogEntry, FakeLogLevel, FakeMentionProviderRecord, FakePluginBehaviorDrivers, FakePluginHarness, FakePluginHost, FakePluginInspectionState, FakePluginLifecycleControls, FakePluginRegistrations, FakeRealtimeSignal, FakeScheduleRecord, FakeSdkCall, FakeSdkHarness, FakeSdkOverrides, FakeServiceRecord };