@get-bb/plugin-sdk 0.4.34 → 0.4.40

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.
@@ -6,7 +6,6 @@ import { join } from "node:path";
6
6
  import Database from "better-sqlite3";
7
7
  import { CronExpressionParser } from "cron-parser";
8
8
  import { Hono } from "hono";
9
- import { z as z4 } from "zod";
10
9
 
11
10
  // ../domain/src/plugin-interaction-limits.ts
12
11
  var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
@@ -290,7 +289,7 @@ var PLUGIN_HTTP_METHODS = /* @__PURE__ */ new Set([
290
289
  "HEAD",
291
290
  "OPTIONS"
292
291
  ]);
293
- var RPC_METHOD_PATTERN = /^[a-zA-Z0-9_-]+$/;
292
+ var RPC_METHOD_PATTERN = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*$/;
294
293
  var BACKGROUND_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
295
294
  var CLI_COMMAND_NAME_PATTERN = /^[a-z0-9-]+$/;
296
295
  var AGENT_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
@@ -307,12 +306,22 @@ var settingsBaseFields = {
307
306
  label: z3.string().min(1),
308
307
  description: z3.string().min(1).optional()
309
308
  };
309
+ var stringSettingSchemaSchema = z3.custom(
310
+ (value) => isStandardSchema(value)
311
+ );
312
+ var booleanSettingSchemaSchema = z3.custom(
313
+ (value) => isStandardSchema(value)
314
+ );
315
+ var numberSettingSchemaSchema = z3.custom(
316
+ (value) => isStandardSchema(value)
317
+ );
310
318
  var settingDescriptorSchema = z3.discriminatedUnion("type", [
311
319
  z3.object({
312
320
  type: z3.literal("string"),
313
321
  ...settingsBaseFields,
314
322
  secret: z3.literal(true).optional(),
315
323
  experimental_multiline: z3.boolean().optional(),
324
+ experimental_schema: stringSettingSchemaSchema.optional(),
316
325
  default: z3.string().optional()
317
326
  }).strict().refine(
318
327
  (descriptor) => !(descriptor.secret === true && descriptor.experimental_multiline === true),
@@ -324,17 +333,26 @@ var settingDescriptorSchema = z3.discriminatedUnion("type", [
324
333
  z3.object({
325
334
  type: z3.literal("boolean"),
326
335
  ...settingsBaseFields,
336
+ experimental_schema: booleanSettingSchemaSchema.optional(),
327
337
  default: z3.boolean().optional()
328
338
  }).strict(),
339
+ z3.object({
340
+ type: z3.literal("number"),
341
+ ...settingsBaseFields,
342
+ experimental_schema: numberSettingSchemaSchema.optional(),
343
+ default: z3.number().finite().optional()
344
+ }).strict(),
329
345
  z3.object({
330
346
  type: z3.literal("select"),
331
347
  ...settingsBaseFields,
332
348
  options: z3.array(z3.string().min(1)).min(1),
349
+ experimental_schema: stringSettingSchemaSchema.optional(),
333
350
  default: z3.string().optional()
334
351
  }).strict(),
335
352
  z3.object({
336
353
  type: z3.literal("project"),
337
354
  ...settingsBaseFields,
355
+ experimental_schema: stringSettingSchemaSchema.optional(),
338
356
  default: z3.string().optional()
339
357
  }).strict()
340
358
  ]);
@@ -363,11 +381,39 @@ function registerSettingDescriptors(target, added) {
363
381
  `default for setting "${key}" must be one of its options`
364
382
  );
365
383
  }
384
+ if (descriptor.default !== void 0) {
385
+ const errors = validateSettingsUpdate(
386
+ { [key]: descriptor },
387
+ { [key]: descriptor.default }
388
+ );
389
+ if (errors.length > 0) {
390
+ throw new Error(`invalid default for setting "${key}": ${errors[0]}`);
391
+ }
392
+ }
366
393
  validated[key] = descriptor;
367
394
  }
368
395
  Object.assign(target, validated);
369
396
  return validated;
370
397
  }
398
+ function settingSchemaError(key, schema, value) {
399
+ if (schema === void 0) return null;
400
+ try {
401
+ const result = schema["~standard"].validate(value);
402
+ if (result instanceof Promise) {
403
+ return `schema for setting "${key}" must validate synchronously`;
404
+ }
405
+ if (result.issues !== void 0) {
406
+ return result.issues[0]?.message ?? `schema for setting "${key}" rejected the value`;
407
+ }
408
+ if (result.value !== value) {
409
+ return `schema for setting "${key}" must not transform its value`;
410
+ }
411
+ return null;
412
+ } catch (error) {
413
+ const detail = error instanceof Error ? error.message : String(error);
414
+ return `schema for setting "${key}" failed: ${detail}`;
415
+ }
416
+ }
371
417
  function validateSettingsUpdate(descriptors, values) {
372
418
  const errors = [];
373
419
  for (const [key, value] of Object.entries(values)) {
@@ -380,6 +426,30 @@ function validateSettingsUpdate(descriptors, values) {
380
426
  if (descriptor.type === "boolean") {
381
427
  if (typeof value !== "boolean") {
382
428
  errors.push(`setting "${key}" expects a boolean`);
429
+ continue;
430
+ }
431
+ const validationError2 = settingSchemaError(
432
+ key,
433
+ descriptor.experimental_schema,
434
+ value
435
+ );
436
+ if (validationError2 !== null) {
437
+ errors.push(validationError2);
438
+ }
439
+ continue;
440
+ }
441
+ if (descriptor.type === "number") {
442
+ if (typeof value !== "number" || !Number.isFinite(value)) {
443
+ errors.push(`setting "${key}" expects a finite number`);
444
+ continue;
445
+ }
446
+ const validationError2 = settingSchemaError(
447
+ key,
448
+ descriptor.experimental_schema,
449
+ value
450
+ );
451
+ if (validationError2 !== null) {
452
+ errors.push(validationError2);
383
453
  }
384
454
  continue;
385
455
  }
@@ -391,6 +461,15 @@ function validateSettingsUpdate(descriptors, values) {
391
461
  errors.push(
392
462
  `setting "${key}" must be one of: ${descriptor.options.join(", ")}`
393
463
  );
464
+ continue;
465
+ }
466
+ const validationError = settingSchemaError(
467
+ key,
468
+ descriptor.experimental_schema,
469
+ value
470
+ );
471
+ if (validationError !== null) {
472
+ errors.push(validationError);
394
473
  }
395
474
  }
396
475
  return errors;
@@ -1294,6 +1373,12 @@ function readRpcMethodContract(method, value) {
1294
1373
  function isZodSchemaLike(value) {
1295
1374
  return typeof value === "object" && value !== null && typeof value.safeParse === "function";
1296
1375
  }
1376
+ function zodSchemaToJsonSchema(schema) {
1377
+ if (typeof schema.toJSONSchema === "function") {
1378
+ return schema.toJSONSchema({ io: "input" });
1379
+ }
1380
+ return z3.toJSONSchema(schema, { io: "input" });
1381
+ }
1297
1382
  var SINGLE_SCHEMA_KEYWORDS = [
1298
1383
  "additionalItems",
1299
1384
  "additionalProperties",
@@ -1705,7 +1790,14 @@ function readSettingsValues(descriptors, stored) {
1705
1790
  const values = {};
1706
1791
  for (const [key, descriptor] of Object.entries(descriptors)) {
1707
1792
  let value = stored.get(key);
1708
- const expected = descriptor.type === "boolean" ? "boolean" : "string";
1793
+ if (descriptor.type === "number" && typeof value === "string") {
1794
+ const legacyNumber = Number(value.trim());
1795
+ value = value.trim().length > 0 && Number.isFinite(legacyNumber) ? legacyNumber : void 0;
1796
+ }
1797
+ if (descriptor.type === "number" && typeof value === "number" && !Number.isFinite(value)) {
1798
+ value = void 0;
1799
+ }
1800
+ const expected = descriptor.type === "boolean" ? "boolean" : descriptor.type === "number" ? "number" : "string";
1709
1801
  if (typeof value !== expected) value = void 0;
1710
1802
  if (descriptor.type === "select" && typeof value === "string" && !descriptor.options.includes(value)) {
1711
1803
  value = void 0;
@@ -2119,20 +2211,57 @@ function createFakePluginHostInternal(options, sharedState) {
2119
2211
  const settingsDescriptors = {};
2120
2212
  const settingsListeners = [];
2121
2213
  const storedSettings = persistentState.storedSettings;
2214
+ async function setSettingsValues(values) {
2215
+ const errors = validateSettingsUpdate(settingsDescriptors, values);
2216
+ if (errors.length > 0) {
2217
+ throw new Error(errors.join("; "));
2218
+ }
2219
+ const prev = readSettingsValues(settingsDescriptors, storedSettings);
2220
+ for (const [key, value] of Object.entries(values)) {
2221
+ if (value === null) storedSettings.delete(key);
2222
+ else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2223
+ storedSettings.set(key, value);
2224
+ } else {
2225
+ throw new Error(`setting "${key}" has an unsupported value`);
2226
+ }
2227
+ }
2228
+ const next = readSettingsValues(settingsDescriptors, storedSettings);
2229
+ if (JSON.stringify(next) === JSON.stringify(prev)) return;
2230
+ for (const listener of settingsListeners) {
2231
+ try {
2232
+ listener(next, prev);
2233
+ } catch (error) {
2234
+ emitLog(
2235
+ "warn",
2236
+ `settings onChange listener failed: ${errorMessage(error)}`
2237
+ );
2238
+ }
2239
+ }
2240
+ }
2122
2241
  const settings = {
2123
2242
  define(descriptors) {
2124
2243
  assertLive();
2125
- registerSettingDescriptors(
2244
+ const validated = registerSettingDescriptors(
2126
2245
  settingsDescriptors,
2127
2246
  descriptors
2128
2247
  );
2129
2248
  return {
2130
2249
  async get() {
2131
2250
  assertLive();
2132
- return readSettingsValues(
2133
- settingsDescriptors,
2134
- storedSettings
2135
- );
2251
+ return readSettingsValues(validated, storedSettings);
2252
+ },
2253
+ async experimental_set(values) {
2254
+ assertLive();
2255
+ const rawValues = {};
2256
+ for (const [key, value] of Object.entries(values)) {
2257
+ rawValues[key] = value;
2258
+ }
2259
+ const errors = validateSettingsUpdate(validated, rawValues);
2260
+ if (errors.length > 0) {
2261
+ throw new Error(errors.join("; "));
2262
+ }
2263
+ await setSettingsValues(rawValues);
2264
+ return readSettingsValues(validated, storedSettings);
2136
2265
  },
2137
2266
  onChange(listener) {
2138
2267
  assertLive();
@@ -2202,7 +2331,7 @@ function createFakePluginHostInternal(options, sharedState) {
2202
2331
  for (const [name, contractValue] of contractEntries) {
2203
2332
  if (!RPC_METHOD_PATTERN.test(name)) {
2204
2333
  throw new Error(
2205
- `invalid rpc method name "${name}" \u2014 use letters, digits, "-" and "_"`
2334
+ `invalid rpc method name "${name}" \u2014 use dot-separated segments with letters, digits, "-" and "_"`
2206
2335
  );
2207
2336
  }
2208
2337
  const methodContract = readRpcMethodContract(name, contractValue);
@@ -2462,9 +2591,7 @@ function createFakePluginHostInternal(options, sharedState) {
2462
2591
  let parse;
2463
2592
  if (isZodSchemaLike(parameters)) {
2464
2593
  try {
2465
- inputSchema = z4.toJSONSchema(parameters, {
2466
- io: "input"
2467
- });
2594
+ inputSchema = zodSchemaToJsonSchema(parameters);
2468
2595
  } catch (error) {
2469
2596
  throw new Error(
2470
2597
  `tool "${name}" parameters look like a zod schema but could not be converted to JSON Schema (${errorMessage(error)}) \u2014 use zod 4, or pass a plain JSON-schema object`
@@ -2554,9 +2681,14 @@ function createFakePluginHostInternal(options, sharedState) {
2554
2681
  );
2555
2682
  }
2556
2683
  };
2684
+ const appUrl = options.appUrl ?? null;
2557
2685
  const loopbackBaseUrl = options.loopbackBaseUrl ?? "http://127.0.0.1:38886";
2558
2686
  const dataDir = options.dataDir ?? "/tmp/bb-fake-data-dir";
2559
2687
  const server = {
2688
+ get experimental_appUrl() {
2689
+ assertLive();
2690
+ return appUrl;
2691
+ },
2560
2692
  get loopbackBaseUrl() {
2561
2693
  assertLive();
2562
2694
  return loopbackBaseUrl;
@@ -2577,6 +2709,7 @@ function createFakePluginHostInternal(options, sharedState) {
2577
2709
  "thread.failed": [],
2578
2710
  "thread.archived": [],
2579
2711
  "thread.deleted": [],
2712
+ "interaction.pending": [],
2580
2713
  "message.queued": [],
2581
2714
  "message.dispatched": [],
2582
2715
  "turn.failed": []
@@ -2916,6 +3049,7 @@ function createFakePluginHostInternal(options, sharedState) {
2916
3049
  "thread.failed": threadEventHandlers["thread.failed"].length,
2917
3050
  "thread.archived": threadEventHandlers["thread.archived"].length,
2918
3051
  "thread.deleted": threadEventHandlers["thread.deleted"].length,
3052
+ "interaction.pending": threadEventHandlers["interaction.pending"].length,
2919
3053
  "message.queued": threadEventHandlers["message.queued"].length,
2920
3054
  "message.dispatched": threadEventHandlers["message.dispatched"].length,
2921
3055
  "turn.failed": threadEventHandlers["turn.failed"].length
@@ -2978,27 +3112,7 @@ function createFakePluginHostInternal(options, sharedState) {
2978
3112
  pending.resolve({ outcome: "cancelled", reason: "user" });
2979
3113
  },
2980
3114
  async setSettings(values) {
2981
- const errors = validateSettingsUpdate(settingsDescriptors, values);
2982
- if (errors.length > 0) {
2983
- throw new Error(errors.join("; "));
2984
- }
2985
- const prev = readSettingsValues(settingsDescriptors, storedSettings);
2986
- for (const [key, value] of Object.entries(values)) {
2987
- if (value === null) storedSettings.delete(key);
2988
- else storedSettings.set(key, value);
2989
- }
2990
- const next = readSettingsValues(settingsDescriptors, storedSettings);
2991
- if (JSON.stringify(next) === JSON.stringify(prev)) return;
2992
- for (const listener of settingsListeners) {
2993
- try {
2994
- listener(next, prev);
2995
- } catch (error) {
2996
- emitLog(
2997
- "warn",
2998
- `settings onChange listener failed: ${errorMessage(error)}`
2999
- );
3000
- }
3001
- }
3115
+ await setSettingsValues(values);
3002
3116
  },
3003
3117
  async callRpc(method, input) {
3004
3118
  const record = rpcHandlers.get(method);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-bb/plugin-sdk",
3
- "version": "0.4.34",
3
+ "version": "0.4.40",
4
4
  "homepage": "https://github.com/get-bb/bb#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/get-bb/bb/issues"