@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.
- package/README.md +10 -0
- package/bundled-types/bb-plugin-sdk-app.d.ts +32 -3
- package/bundled-types/bb-plugin-sdk-host.d.ts +1 -1
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +23 -9
- package/bundled-types/bb-plugin-sdk-internal-plugin-app-collector.d.ts +2 -1
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +3 -2
- package/bundled-types/bb-plugin-sdk-testing.d.ts +6 -2
- package/bundled-types/bb-plugin-sdk.d.ts +289 -92
- package/dist/internal/host-policy.js +89 -2
- package/dist/internal/plugin-app-collector.js +11 -0
- package/dist/provider-bridge-acp.js +135 -26
- package/dist/testing/app.js +11 -0
- package/dist/testing/index.js +147 -33
- package/package.json +1 -1
package/dist/testing/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2134
|
-
|
|
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 =
|
|
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
|
-
|
|
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);
|