@get-bb/plugin-sdk 0.4.35 → 0.4.45

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.
@@ -289,10 +289,46 @@ var PLUGIN_HTTP_METHODS = /* @__PURE__ */ new Set([
289
289
  "HEAD",
290
290
  "OPTIONS"
291
291
  ]);
292
- var RPC_METHOD_PATTERN = /^[a-zA-Z0-9_-]+$/;
292
+ var RPC_METHOD_PATTERN = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*$/;
293
293
  var BACKGROUND_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
294
294
  var CLI_COMMAND_NAME_PATTERN = /^[a-z0-9-]+$/;
295
295
  var AGENT_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
296
+ var PLUGIN_PROVIDER_ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
297
+ var PLUGIN_PROVIDER_ENV_MAX_ENTRIES = 32;
298
+ var pluginProviderEnvEntrySchema = z3.object({
299
+ name: z3.string().regex(PLUGIN_PROVIDER_ENV_NAME_PATTERN),
300
+ value: z3.union([
301
+ z3.string(),
302
+ z3.object({ serverPath: z3.string().startsWith("/") }).strict()
303
+ ]),
304
+ reason: z3.string(),
305
+ secret: z3.boolean()
306
+ }).strict();
307
+ var pluginProviderEnvEntriesSchema = z3.array(pluginProviderEnvEntrySchema).max(PLUGIN_PROVIDER_ENV_MAX_ENTRIES).superRefine((entries, context) => {
308
+ const names = /* @__PURE__ */ new Set();
309
+ for (let index = 0; index < entries.length; index += 1) {
310
+ const name = entries[index]?.name;
311
+ if (name !== void 0 && names.has(name)) {
312
+ context.addIssue({
313
+ code: "custom",
314
+ path: [index, "name"],
315
+ message: "must be unique within one resolver"
316
+ });
317
+ }
318
+ if (name !== void 0) names.add(name);
319
+ }
320
+ });
321
+ function validatePluginProviderEnvEntries(value) {
322
+ const parsed = pluginProviderEnvEntriesSchema.safeParse(value);
323
+ if (!parsed.success) {
324
+ const issue = parsed.error.issues[0];
325
+ const path = issue?.path.length ? `[${issue.path.join(".")}] ` : "";
326
+ throw new Error(
327
+ `provider environment contribution ${path}${issue?.message ?? "is invalid"}`
328
+ );
329
+ }
330
+ return parsed.data;
331
+ }
296
332
  var PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS = 4096;
297
333
  var PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS = 80;
298
334
  var PLUGIN_AGENT_SELECTION_MAX_IDS = 256;
@@ -312,6 +348,9 @@ var stringSettingSchemaSchema = z3.custom(
312
348
  var booleanSettingSchemaSchema = z3.custom(
313
349
  (value) => isStandardSchema(value)
314
350
  );
351
+ var numberSettingSchemaSchema = z3.custom(
352
+ (value) => isStandardSchema(value)
353
+ );
315
354
  var settingDescriptorSchema = z3.discriminatedUnion("type", [
316
355
  z3.object({
317
356
  type: z3.literal("string"),
@@ -333,6 +372,12 @@ var settingDescriptorSchema = z3.discriminatedUnion("type", [
333
372
  experimental_schema: booleanSettingSchemaSchema.optional(),
334
373
  default: z3.boolean().optional()
335
374
  }).strict(),
375
+ z3.object({
376
+ type: z3.literal("number"),
377
+ ...settingsBaseFields,
378
+ experimental_schema: numberSettingSchemaSchema.optional(),
379
+ default: z3.number().finite().optional()
380
+ }).strict(),
336
381
  z3.object({
337
382
  type: z3.literal("select"),
338
383
  ...settingsBaseFields,
@@ -429,6 +474,21 @@ function validateSettingsUpdate(descriptors, values) {
429
474
  }
430
475
  continue;
431
476
  }
477
+ if (descriptor.type === "number") {
478
+ if (typeof value !== "number" || !Number.isFinite(value)) {
479
+ errors.push(`setting "${key}" expects a finite number`);
480
+ continue;
481
+ }
482
+ const validationError2 = settingSchemaError(
483
+ key,
484
+ descriptor.experimental_schema,
485
+ value
486
+ );
487
+ if (validationError2 !== null) {
488
+ errors.push(validationError2);
489
+ }
490
+ continue;
491
+ }
432
492
  if (typeof value !== "string") {
433
493
  errors.push(`setting "${key}" expects a string`);
434
494
  continue;
@@ -1766,7 +1826,14 @@ function readSettingsValues(descriptors, stored) {
1766
1826
  const values = {};
1767
1827
  for (const [key, descriptor] of Object.entries(descriptors)) {
1768
1828
  let value = stored.get(key);
1769
- const expected = descriptor.type === "boolean" ? "boolean" : "string";
1829
+ if (descriptor.type === "number" && typeof value === "string") {
1830
+ const legacyNumber = Number(value.trim());
1831
+ value = value.trim().length > 0 && Number.isFinite(legacyNumber) ? legacyNumber : void 0;
1832
+ }
1833
+ if (descriptor.type === "number" && typeof value === "number" && !Number.isFinite(value)) {
1834
+ value = void 0;
1835
+ }
1836
+ const expected = descriptor.type === "boolean" ? "boolean" : descriptor.type === "number" ? "number" : "string";
1770
1837
  if (typeof value !== expected) value = void 0;
1771
1838
  if (descriptor.type === "select" && typeof value === "string" && !descriptor.options.includes(value)) {
1772
1839
  value = void 0;
@@ -2190,7 +2257,7 @@ function createFakePluginHostInternal(options, sharedState) {
2190
2257
  const prev = readSettingsValues(settingsDescriptors, storedSettings);
2191
2258
  for (const [key, value] of Object.entries(values)) {
2192
2259
  if (value === null) storedSettings.delete(key);
2193
- else if (typeof value === "string" || typeof value === "boolean") {
2260
+ else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2194
2261
  storedSettings.set(key, value);
2195
2262
  } else {
2196
2263
  throw new Error(`setting "${key}" has an unsupported value`);
@@ -2302,7 +2369,7 @@ function createFakePluginHostInternal(options, sharedState) {
2302
2369
  for (const [name, contractValue] of contractEntries) {
2303
2370
  if (!RPC_METHOD_PATTERN.test(name)) {
2304
2371
  throw new Error(
2305
- `invalid rpc method name "${name}" \u2014 use letters, digits, "-" and "_"`
2372
+ `invalid rpc method name "${name}" \u2014 use dot-separated segments with letters, digits, "-" and "_"`
2306
2373
  );
2307
2374
  }
2308
2375
  const methodContract = readRpcMethodContract(name, contractValue);
@@ -2437,6 +2504,8 @@ function createFakePluginHostInternal(options, sharedState) {
2437
2504
  };
2438
2505
  const agentTools = [];
2439
2506
  const providerRegistrations = [];
2507
+ const providerEnvResolvers = /* @__PURE__ */ new Map();
2508
+ const providerEnvHealthResolvers = /* @__PURE__ */ new Map();
2440
2509
  let agentConfigurationProvider = null;
2441
2510
  let instructionProvider = null;
2442
2511
  function registerProviderDeclaration(declaration) {
@@ -2652,9 +2721,14 @@ function createFakePluginHostInternal(options, sharedState) {
2652
2721
  );
2653
2722
  }
2654
2723
  };
2724
+ const appUrl = options.appUrl ?? null;
2655
2725
  const loopbackBaseUrl = options.loopbackBaseUrl ?? "http://127.0.0.1:38886";
2656
2726
  const dataDir = options.dataDir ?? "/tmp/bb-fake-data-dir";
2657
2727
  const server = {
2728
+ get experimental_appUrl() {
2729
+ assertLive();
2730
+ return appUrl;
2731
+ },
2658
2732
  get loopbackBaseUrl() {
2659
2733
  assertLive();
2660
2734
  return loopbackBaseUrl;
@@ -2675,6 +2749,7 @@ function createFakePluginHostInternal(options, sharedState) {
2675
2749
  "thread.failed": [],
2676
2750
  "thread.archived": [],
2677
2751
  "thread.deleted": [],
2752
+ "interaction.pending": [],
2678
2753
  "message.queued": [],
2679
2754
  "message.dispatched": [],
2680
2755
  "turn.failed": []
@@ -2896,6 +2971,44 @@ function createFakePluginHostInternal(options, sharedState) {
2896
2971
  const providers = {
2897
2972
  register(declaration) {
2898
2973
  return registerProviderDeclaration(declaration);
2974
+ },
2975
+ experimental_contributeEnv(providerId, resolve2) {
2976
+ assertLive();
2977
+ if (typeof providerId !== "string" || providerId.trim().length === 0) {
2978
+ throw new Error(
2979
+ "provider environment contribution requires a provider id"
2980
+ );
2981
+ }
2982
+ if (providerEnvResolvers.has(providerId)) {
2983
+ throw new Error(
2984
+ `provider environment contribution for "${providerId}" is already registered`
2985
+ );
2986
+ }
2987
+ if (typeof resolve2 !== "function") {
2988
+ throw new Error(
2989
+ "provider environment contribution requires a resolver function"
2990
+ );
2991
+ }
2992
+ providerEnvResolvers.set(providerId, resolve2);
2993
+ },
2994
+ experimental_contributeEnvHealth(providerId, resolve2) {
2995
+ assertLive();
2996
+ if (typeof providerId !== "string" || providerId.trim().length === 0) {
2997
+ throw new Error(
2998
+ "provider environment health contribution requires a provider id"
2999
+ );
3000
+ }
3001
+ if (providerEnvHealthResolvers.has(providerId)) {
3002
+ throw new Error(
3003
+ `provider environment health contribution for "${providerId}" is already registered`
3004
+ );
3005
+ }
3006
+ if (typeof resolve2 !== "function") {
3007
+ throw new Error(
3008
+ "provider environment health contribution requires a resolver function"
3009
+ );
3010
+ }
3011
+ providerEnvHealthResolvers.set(providerId, resolve2);
2899
3012
  }
2900
3013
  };
2901
3014
  const experimental_hooks = {
@@ -3014,6 +3127,7 @@ function createFakePluginHostInternal(options, sharedState) {
3014
3127
  "thread.failed": threadEventHandlers["thread.failed"].length,
3015
3128
  "thread.archived": threadEventHandlers["thread.archived"].length,
3016
3129
  "thread.deleted": threadEventHandlers["thread.deleted"].length,
3130
+ "interaction.pending": threadEventHandlers["interaction.pending"].length,
3017
3131
  "message.queued": threadEventHandlers["message.queued"].length,
3018
3132
  "message.dispatched": threadEventHandlers["message.dispatched"].length,
3019
3133
  "turn.failed": threadEventHandlers["turn.failed"].length
@@ -3024,6 +3138,8 @@ function createFakePluginHostInternal(options, sharedState) {
3024
3138
  },
3025
3139
  mentionProviders,
3026
3140
  providerRegistrations,
3141
+ providerEnvResolvers,
3142
+ providerEnvHealthResolvers,
3027
3143
  aiServiceRegistrations
3028
3144
  },
3029
3145
  get pendingInteractions() {
@@ -3041,6 +3157,43 @@ function createFakePluginHostInternal(options, sharedState) {
3041
3157
  await handler({ hostId });
3042
3158
  }
3043
3159
  },
3160
+ async resolveProviderEnv(providerId, context) {
3161
+ assertLive();
3162
+ const resolve2 = providerEnvResolvers.get(providerId);
3163
+ if (resolve2 === void 0) return [];
3164
+ try {
3165
+ return validatePluginProviderEnvEntries(await resolve2(context));
3166
+ } catch (error) {
3167
+ emitLog(
3168
+ "warn",
3169
+ `provider environment contribution failed: ${error instanceof Error ? error.message : String(error)}`
3170
+ );
3171
+ return [];
3172
+ }
3173
+ },
3174
+ async resolveProviderEnvHealth(providerId, context) {
3175
+ assertLive();
3176
+ if (!providerEnvResolvers.has(providerId)) return null;
3177
+ const resolve2 = providerEnvHealthResolvers.get(providerId);
3178
+ if (resolve2 === void 0) return null;
3179
+ try {
3180
+ const value = await resolve2(context);
3181
+ if (value === null) return null;
3182
+ if (value.label.trim().length === 0) {
3183
+ throw new Error("label must not be empty");
3184
+ }
3185
+ if (value.statusMessage.trim().length === 0) {
3186
+ throw new Error("statusMessage must not be empty");
3187
+ }
3188
+ return value;
3189
+ } catch (error) {
3190
+ emitLog(
3191
+ "warn",
3192
+ `provider environment health contribution failed: ${error instanceof Error ? error.message : String(error)}`
3193
+ );
3194
+ return null;
3195
+ }
3196
+ },
3044
3197
  async experimental_emitHostSignal(hostId, signal, payload) {
3045
3198
  assertLive();
3046
3199
  if (hostId.trim().length === 0) {
@@ -3312,6 +3465,151 @@ function makeThreadResponse(overrides = {}) {
3312
3465
  ...overrides
3313
3466
  };
3314
3467
  }
3468
+ function makePluginAgentConfigurationContext(overrides = {}) {
3469
+ const context = {
3470
+ thread: {
3471
+ id: "thread-test",
3472
+ title: null,
3473
+ parentThreadId: null,
3474
+ sourceThreadId: null
3475
+ },
3476
+ project: {
3477
+ id: "project-test",
3478
+ kind: "standard",
3479
+ name: "Test project",
3480
+ gitRemoteUrl: null
3481
+ },
3482
+ environment: {
3483
+ id: "environment-test",
3484
+ name: null,
3485
+ path: "/tmp/test",
3486
+ workspaceProvisionType: "unmanaged",
3487
+ branchName: null
3488
+ },
3489
+ host: { id: "host-test", name: "Test host" },
3490
+ provider: {
3491
+ id: "test-provider",
3492
+ model: "test-model",
3493
+ capabilities: { supportsNativeUserQuestion: false }
3494
+ },
3495
+ origin: { kind: null, pluginId: null }
3496
+ };
3497
+ return {
3498
+ thread: { ...context.thread, ...overrides.thread },
3499
+ project: { ...context.project, ...overrides.project },
3500
+ environment: { ...context.environment, ...overrides.environment },
3501
+ host: { ...context.host, ...overrides.host },
3502
+ provider: {
3503
+ ...context.provider,
3504
+ ...overrides.provider,
3505
+ capabilities: {
3506
+ ...context.provider.capabilities,
3507
+ ...overrides.provider?.capabilities
3508
+ }
3509
+ },
3510
+ origin: { ...context.origin, ...overrides.origin }
3511
+ };
3512
+ }
3513
+ function makeMessageDispatchHookContext(overrides = {}) {
3514
+ const context = {
3515
+ thread: makeThreadResponse({ status: "pending" }),
3516
+ project: {
3517
+ id: "project-1",
3518
+ kind: "standard",
3519
+ name: "Test project",
3520
+ gitRemoteUrl: null,
3521
+ createdAt: 0,
3522
+ updatedAt: 0
3523
+ },
3524
+ environment: null,
3525
+ host: null,
3526
+ input: { blocks: [], text: "Test message" },
3527
+ requestedExecution: {
3528
+ providerId: "test-provider",
3529
+ model: null,
3530
+ reasoningLevel: null,
3531
+ serviceTier: null,
3532
+ permissionMode: null
3533
+ },
3534
+ executionSources: {
3535
+ providerId: null,
3536
+ model: null,
3537
+ reasoningLevel: null,
3538
+ serviceTier: null,
3539
+ permissionMode: null
3540
+ },
3541
+ attempt: "start-turn",
3542
+ queuedMessage: null,
3543
+ origin: null,
3544
+ originPluginId: null,
3545
+ startedOnBehalfOf: null,
3546
+ parentThreadId: null
3547
+ };
3548
+ const environmentDefaults = {
3549
+ id: "environment-1",
3550
+ name: null,
3551
+ projectId: "project-1",
3552
+ hostId: "host-1",
3553
+ path: "/tmp/test",
3554
+ managed: true,
3555
+ isGitRepo: true,
3556
+ isWorktree: false,
3557
+ workspaceProvisionType: "unmanaged",
3558
+ branchName: "main",
3559
+ baseBranch: null,
3560
+ defaultBranch: "main",
3561
+ mergeBaseBranch: null,
3562
+ status: "ready",
3563
+ createdAt: 0,
3564
+ updatedAt: 0
3565
+ };
3566
+ const hostDefaults = {
3567
+ id: "host-1",
3568
+ name: "Test host",
3569
+ type: "persistent",
3570
+ status: "connected",
3571
+ maxPermissionMode: "full",
3572
+ lastSeenAt: null,
3573
+ lastRejectedProtocolVersion: null,
3574
+ createdAt: 0,
3575
+ updatedAt: 0
3576
+ };
3577
+ const project = { ...context.project, ...overrides.project };
3578
+ const host = overrides.host === void 0 ? context.host : overrides.host === null ? null : { ...hostDefaults, ...overrides.host };
3579
+ const environment = overrides.environment === void 0 ? context.environment : overrides.environment === null ? null : {
3580
+ ...environmentDefaults,
3581
+ projectId: project.id,
3582
+ hostId: host?.id ?? environmentDefaults.hostId,
3583
+ ...overrides.environment
3584
+ };
3585
+ const thread = {
3586
+ ...context.thread,
3587
+ projectId: project.id,
3588
+ environmentId: environment?.id ?? null,
3589
+ ...overrides.thread
3590
+ };
3591
+ return {
3592
+ ...context,
3593
+ ...overrides,
3594
+ thread,
3595
+ project,
3596
+ environment,
3597
+ host,
3598
+ input: { ...context.input, ...overrides.input },
3599
+ requestedExecution: {
3600
+ ...context.requestedExecution,
3601
+ ...overrides.requestedExecution
3602
+ },
3603
+ executionSources: {
3604
+ ...context.executionSources,
3605
+ ...overrides.executionSources
3606
+ },
3607
+ queuedMessage: overrides.queuedMessage === void 0 ? context.queuedMessage : overrides.queuedMessage === null ? null : makeQueueEntry({
3608
+ threadId: thread.id,
3609
+ ...overrides.queuedMessage
3610
+ })
3611
+ };
3612
+ }
3315
3613
  function makeQueueEntry(overrides = {}) {
3316
3614
  return {
3317
3615
  id: "queued_1",
@@ -3449,6 +3747,8 @@ export {
3449
3747
  createFakePluginHost,
3450
3748
  createFakeSdk,
3451
3749
  scanPublicSdkOnly as experimental_scanPublicSdkOnly,
3750
+ makeMessageDispatchHookContext,
3751
+ makePluginAgentConfigurationContext,
3452
3752
  makeQueueEntry,
3453
3753
  makeThreadResponse,
3454
3754
  makeTurnFailedEvent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-bb/plugin-sdk",
3
- "version": "0.4.35",
3
+ "version": "0.4.45",
4
4
  "homepage": "https://github.com/get-bb/bb#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/get-bb/bb/issues"