@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.
- package/README.md +3 -3
- package/bundled-types/bb-plugin-sdk-ai-services.d.ts +141 -0
- package/bundled-types/bb-plugin-sdk-app.d.ts +144 -21
- package/bundled-types/bb-plugin-sdk-host.d.ts +479 -2
- package/bundled-types/bb-plugin-sdk-internal-composer-customization-validation.d.ts +10 -1
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +339 -32
- package/bundled-types/bb-plugin-sdk-internal-plugin-app-collector.d.ts +2 -1
- package/bundled-types/bb-plugin-sdk-provider-bridge-acp.d.ts +807 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts +1742 -211
- package/bundled-types/bb-plugin-sdk-provider-bridge.d.ts +2985 -2442
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +4 -3
- package/bundled-types/bb-plugin-sdk-testing.d.ts +551 -9
- package/bundled-types/bb-plugin-sdk.d.ts +1335 -1350
- package/dist/ai-services.js +91 -0
- package/dist/app.js +2 -2
- package/dist/host.js +14547 -1
- package/dist/internal/composer-customization-validation.js +11 -0
- package/dist/internal/host-policy.js +656 -91
- package/dist/internal/plugin-app-collector.js +58 -14
- package/dist/provider-bridge-acp.js +9753 -0
- package/dist/provider-bridge-testing.js +3355 -1771
- package/dist/provider-bridge-worker-entry.mjs +917 -0
- package/dist/provider-bridge.js +2255 -3718
- package/dist/replay-provider-child.mjs +650 -0
- package/dist/testing/app.js +63 -19
- package/dist/testing/index.js +803 -115
- package/package.json +14 -1
package/dist/testing/index.js
CHANGED
|
@@ -5,18 +5,34 @@ import { join } from "node:path";
|
|
|
5
5
|
import Database from "better-sqlite3";
|
|
6
6
|
import { CronExpressionParser } from "cron-parser";
|
|
7
7
|
import { Hono } from "hono";
|
|
8
|
-
import { z as
|
|
8
|
+
import { z as z4 } from "zod";
|
|
9
9
|
|
|
10
10
|
// ../domain/src/plugin-interaction-limits.ts
|
|
11
11
|
var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
|
|
12
|
+
var PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
|
|
12
13
|
|
|
13
14
|
// src/internal/host-policy.ts
|
|
14
|
-
import { z as
|
|
15
|
+
import { z as z3 } from "zod";
|
|
15
16
|
|
|
16
17
|
// ../domain/src/plugin-icon.ts
|
|
17
18
|
function isPluginOwnedIconPath(icon) {
|
|
18
19
|
return icon.startsWith("./");
|
|
19
20
|
}
|
|
21
|
+
var PLUGIN_ICON_MAX_BYTES = 32 * 1024;
|
|
22
|
+
var NAMESPACED_GLYPH_PATTERN = /^[a-z0-9-]+\/[a-z0-9][a-z0-9-]*$/u;
|
|
23
|
+
function isNamespacedGlyph(glyph) {
|
|
24
|
+
return NAMESPACED_GLYPH_PATTERN.test(glyph);
|
|
25
|
+
}
|
|
26
|
+
function parseNamespacedGlyph(glyph) {
|
|
27
|
+
if (!isNamespacedGlyph(glyph)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const separator = glyph.indexOf("/");
|
|
31
|
+
return {
|
|
32
|
+
pluginId: glyph.slice(0, separator),
|
|
33
|
+
name: glyph.slice(separator + 1)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
20
36
|
|
|
21
37
|
// ../domain/src/plugin-cli.ts
|
|
22
38
|
var RESERVED_BB_CLI_COMMANDS = [
|
|
@@ -38,6 +54,213 @@ import { z } from "zod";
|
|
|
38
54
|
var PROVIDER_FORK_VALUES = ["none", "tip", "checkpoint"];
|
|
39
55
|
var providerForkSchema = z.enum(PROVIDER_FORK_VALUES);
|
|
40
56
|
|
|
57
|
+
// ../domain/src/provider-skill-roots.ts
|
|
58
|
+
function isAbsoluteProviderSkillRootPath(value) {
|
|
59
|
+
if (value.length === 0) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const normalized = value.replaceAll("\\", "/");
|
|
63
|
+
const drive = /^[a-zA-Z]:\//u.exec(normalized);
|
|
64
|
+
const rest = drive ? normalized.slice(drive[0].length) : normalized.slice(1);
|
|
65
|
+
if (!drive && !normalized.startsWith("/")) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return rest.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
|
|
69
|
+
}
|
|
70
|
+
function isRelativeProviderSkillRootPath(value) {
|
|
71
|
+
if (value.length === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const normalized = value.replaceAll("\\", "/");
|
|
75
|
+
return !normalized.startsWith("/") && !/^[a-zA-Z]:\//u.test(normalized) && normalized.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ../domain/src/native-roots.ts
|
|
79
|
+
import { z as z2 } from "zod";
|
|
80
|
+
var PROVIDER_NATIVE_ROOTS_MAX = 32;
|
|
81
|
+
var PROVIDER_NATIVE_ROOT_NAME_PREFIX_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,62}:$/u;
|
|
82
|
+
var nativeRootNamePrefixSchema = z2.string().refine(
|
|
83
|
+
(value) => value === "" || PROVIDER_NATIVE_ROOT_NAME_PREFIX_PATTERN.test(value),
|
|
84
|
+
"A root name prefix is a plugin-name-like token ending in ':'"
|
|
85
|
+
);
|
|
86
|
+
var nativeRootManifestPathSchema = z2.string().min(1).refine(
|
|
87
|
+
isRelativeProviderSkillRootPath,
|
|
88
|
+
"A manifest marker is a relative path without dot segments"
|
|
89
|
+
);
|
|
90
|
+
var relativeNativeRootPathSchema = z2.string().min(1).refine(
|
|
91
|
+
isRelativeProviderSkillRootPath,
|
|
92
|
+
"Roots must be relative paths without dot segments"
|
|
93
|
+
);
|
|
94
|
+
var absoluteNativeRootPathSchema = z2.string().min(1).refine(
|
|
95
|
+
isAbsoluteProviderSkillRootPath,
|
|
96
|
+
"Absolute roots must be absolute paths without dot segments"
|
|
97
|
+
);
|
|
98
|
+
var providerNativeRootInputSchema = z2.union([
|
|
99
|
+
z2.string().min(1),
|
|
100
|
+
z2.object({
|
|
101
|
+
path: z2.string().min(1),
|
|
102
|
+
/** Skills nest in subdirectories (the agent scans recursively). */
|
|
103
|
+
recursive: z2.boolean().optional(),
|
|
104
|
+
/**
|
|
105
|
+
* Scan the same relative directory in every ancestor of the workspace
|
|
106
|
+
* up to the repository root (`project` roots only).
|
|
107
|
+
*/
|
|
108
|
+
ancestors: z2.boolean().optional(),
|
|
109
|
+
/**
|
|
110
|
+
* Prepended to every name under the root, a vendor plugin's
|
|
111
|
+
* `plugin-name:`; a prefixed root is listed as a plugin root.
|
|
112
|
+
*/
|
|
113
|
+
namePrefix: nativeRootNamePrefixSchema.optional(),
|
|
114
|
+
/**
|
|
115
|
+
* A file, relative to a skill directory under this root, that marks the
|
|
116
|
+
* directory as a vendor plugin rather than a skill (Claude's
|
|
117
|
+
* `.claude-plugin/plugin.json`): bb skips such a directory. The plugin
|
|
118
|
+
* that knows the vendor layout declares it; core names no vendor path.
|
|
119
|
+
*/
|
|
120
|
+
skipIfManifest: nativeRootManifestPathSchema.optional()
|
|
121
|
+
}).strict()
|
|
122
|
+
]);
|
|
123
|
+
var providerNativeRootsInputSchema = z2.object({
|
|
124
|
+
user: z2.array(providerNativeRootInputSchema).optional(),
|
|
125
|
+
project: z2.array(providerNativeRootInputSchema).optional()
|
|
126
|
+
}).strict();
|
|
127
|
+
var providerNativeRootSchema = z2.object({
|
|
128
|
+
path: z2.string().min(1),
|
|
129
|
+
recursive: z2.boolean(),
|
|
130
|
+
ancestors: z2.boolean(),
|
|
131
|
+
namePrefix: nativeRootNamePrefixSchema,
|
|
132
|
+
/** Absent: every skill-shaped directory under the root is a skill. */
|
|
133
|
+
skipIfManifest: nativeRootManifestPathSchema.optional()
|
|
134
|
+
}).strict();
|
|
135
|
+
function uniqueByPath(roots) {
|
|
136
|
+
return new Set(roots.map((root) => root.path)).size === roots.length;
|
|
137
|
+
}
|
|
138
|
+
function nativeRootSideSchema(side) {
|
|
139
|
+
return z2.array(
|
|
140
|
+
providerNativeRootSchema.extend({ path: relativeNativeRootPathSchema }).superRefine((root, context) => {
|
|
141
|
+
if (root.ancestors && side !== "project") {
|
|
142
|
+
context.addIssue({
|
|
143
|
+
code: "custom",
|
|
144
|
+
message: "Only project roots may walk ancestors"
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
).max(PROVIDER_NATIVE_ROOTS_MAX).refine(uniqueByPath, "Roots must not repeat a path");
|
|
149
|
+
}
|
|
150
|
+
var providerNativeRootsSchema = z2.object({
|
|
151
|
+
user: nativeRootSideSchema("user"),
|
|
152
|
+
project: nativeRootSideSchema("project")
|
|
153
|
+
}).strict();
|
|
154
|
+
var EMPTY_PROVIDER_NATIVE_ROOTS = Object.freeze({
|
|
155
|
+
user: Object.freeze([]),
|
|
156
|
+
project: Object.freeze([])
|
|
157
|
+
});
|
|
158
|
+
function normalizeProviderNativeRoot(entry) {
|
|
159
|
+
if (typeof entry === "string") {
|
|
160
|
+
return { path: entry, recursive: false, ancestors: false, namePrefix: "" };
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
path: entry.path,
|
|
164
|
+
recursive: entry.recursive ?? false,
|
|
165
|
+
ancestors: entry.ancestors ?? false,
|
|
166
|
+
namePrefix: entry.namePrefix ?? "",
|
|
167
|
+
...entry.skipIfManifest === void 0 ? {} : { skipIfManifest: entry.skipIfManifest }
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function normalizeProviderNativeRoots(roots) {
|
|
171
|
+
return {
|
|
172
|
+
user: (roots?.user ?? []).map(normalizeProviderNativeRoot),
|
|
173
|
+
project: (roots?.project ?? []).map(normalizeProviderNativeRoot)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
var providerResolvedNativeRootShapeSchema = z2.enum([
|
|
177
|
+
"skills",
|
|
178
|
+
"skill",
|
|
179
|
+
"skill-file",
|
|
180
|
+
"commands",
|
|
181
|
+
"command-file"
|
|
182
|
+
]);
|
|
183
|
+
var resolvedNativeRootFieldsSchema = z2.object({
|
|
184
|
+
path: absoluteNativeRootPathSchema,
|
|
185
|
+
origin: z2.enum(["user", "project"]),
|
|
186
|
+
recursive: z2.boolean(),
|
|
187
|
+
/** Only with origin `project`, for a path inside the workspace. */
|
|
188
|
+
ancestors: z2.boolean(),
|
|
189
|
+
namePrefix: nativeRootNamePrefixSchema,
|
|
190
|
+
shape: providerResolvedNativeRootShapeSchema,
|
|
191
|
+
/**
|
|
192
|
+
* `skill-file` only: the skill name when the file's frontmatter names
|
|
193
|
+
* none. A vendor plugin's root SKILL.md takes the plugin's name; absent
|
|
194
|
+
* means the parent directory's name.
|
|
195
|
+
*/
|
|
196
|
+
fallbackName: z2.string().min(1).optional(),
|
|
197
|
+
/**
|
|
198
|
+
* `skills` only: a file, relative to a skill directory under this root,
|
|
199
|
+
* that marks the directory as a vendor plugin rather than a skill; the
|
|
200
|
+
* daemon skips such a directory (see the declared root's `skipIfManifest`).
|
|
201
|
+
*/
|
|
202
|
+
skipIfManifest: nativeRootManifestPathSchema.optional()
|
|
203
|
+
}).strict();
|
|
204
|
+
var providerResolvedNativeRootSchema = resolvedNativeRootFieldsSchema.superRefine((root, context) => {
|
|
205
|
+
if (root.skipIfManifest !== void 0 && root.shape !== "skills") {
|
|
206
|
+
context.addIssue({
|
|
207
|
+
code: "custom",
|
|
208
|
+
message: "Only a skills root carries a manifest marker"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (root.ancestors && root.origin !== "project") {
|
|
212
|
+
context.addIssue({
|
|
213
|
+
code: "custom",
|
|
214
|
+
message: "Only project roots may walk ancestors"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (root.fallbackName !== void 0 && root.shape !== "skill-file") {
|
|
218
|
+
context.addIssue({
|
|
219
|
+
code: "custom",
|
|
220
|
+
message: "Only a skill-file root carries a fallback name"
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
var providerResolvedNativeRootInputSchema = resolvedNativeRootFieldsSchema.partial({
|
|
225
|
+
recursive: true,
|
|
226
|
+
ancestors: true,
|
|
227
|
+
namePrefix: true,
|
|
228
|
+
shape: true
|
|
229
|
+
});
|
|
230
|
+
var resolvedSkillShapes = /* @__PURE__ */ new Set([
|
|
231
|
+
"skills",
|
|
232
|
+
"skill",
|
|
233
|
+
"skill-file"
|
|
234
|
+
]);
|
|
235
|
+
var resolvedCommandShapes = /* @__PURE__ */ new Set([
|
|
236
|
+
"commands",
|
|
237
|
+
"command-file"
|
|
238
|
+
]);
|
|
239
|
+
var PROVIDER_RESOLVED_NATIVE_ROOTS_MAX = 256;
|
|
240
|
+
var providerResolvedNativeRootsSchema = z2.object({
|
|
241
|
+
skills: z2.array(
|
|
242
|
+
providerResolvedNativeRootSchema.refine(
|
|
243
|
+
(root) => resolvedSkillShapes.has(root.shape),
|
|
244
|
+
"A skills root needs a skill shape"
|
|
245
|
+
)
|
|
246
|
+
).max(PROVIDER_RESOLVED_NATIVE_ROOTS_MAX),
|
|
247
|
+
commands: z2.array(
|
|
248
|
+
providerResolvedNativeRootSchema.refine(
|
|
249
|
+
(root) => resolvedCommandShapes.has(root.shape),
|
|
250
|
+
"A commands root needs a command shape"
|
|
251
|
+
)
|
|
252
|
+
).max(PROVIDER_RESOLVED_NATIVE_ROOTS_MAX)
|
|
253
|
+
}).strict();
|
|
254
|
+
var EMPTY_PROVIDER_RESOLVED_NATIVE_ROOTS = Object.freeze({
|
|
255
|
+
skills: Object.freeze([]),
|
|
256
|
+
commands: Object.freeze([])
|
|
257
|
+
});
|
|
258
|
+
var providerNativeRootSetSchema = z2.object({
|
|
259
|
+
skills: providerNativeRootsSchema,
|
|
260
|
+
commands: providerNativeRootsSchema,
|
|
261
|
+
resolved: providerResolvedNativeRootsSchema
|
|
262
|
+
}).strict();
|
|
263
|
+
|
|
41
264
|
// src/backend-contract.ts
|
|
42
265
|
var PLUGIN_CLI_OUTPUT_MAX_BYTES = 1024 * 1024;
|
|
43
266
|
|
|
@@ -69,31 +292,38 @@ var PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,63}$/;
|
|
|
69
292
|
var PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES = 64 * 1024;
|
|
70
293
|
var SETTING_KEY_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
71
294
|
var settingsBaseFields = {
|
|
72
|
-
label:
|
|
73
|
-
description:
|
|
295
|
+
label: z3.string().min(1),
|
|
296
|
+
description: z3.string().min(1).optional()
|
|
74
297
|
};
|
|
75
|
-
var settingDescriptorSchema =
|
|
76
|
-
|
|
77
|
-
type:
|
|
298
|
+
var settingDescriptorSchema = z3.discriminatedUnion("type", [
|
|
299
|
+
z3.object({
|
|
300
|
+
type: z3.literal("string"),
|
|
78
301
|
...settingsBaseFields,
|
|
79
|
-
secret:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
302
|
+
secret: z3.literal(true).optional(),
|
|
303
|
+
experimental_multiline: z3.boolean().optional(),
|
|
304
|
+
default: z3.string().optional()
|
|
305
|
+
}).strict().refine(
|
|
306
|
+
(descriptor) => !(descriptor.secret === true && descriptor.experimental_multiline === true),
|
|
307
|
+
{
|
|
308
|
+
message: "a secret setting cannot be experimental_multiline",
|
|
309
|
+
path: ["experimental_multiline"]
|
|
310
|
+
}
|
|
311
|
+
),
|
|
312
|
+
z3.object({
|
|
313
|
+
type: z3.literal("boolean"),
|
|
84
314
|
...settingsBaseFields,
|
|
85
|
-
default:
|
|
315
|
+
default: z3.boolean().optional()
|
|
86
316
|
}).strict(),
|
|
87
|
-
|
|
88
|
-
type:
|
|
317
|
+
z3.object({
|
|
318
|
+
type: z3.literal("select"),
|
|
89
319
|
...settingsBaseFields,
|
|
90
|
-
options:
|
|
91
|
-
default:
|
|
320
|
+
options: z3.array(z3.string().min(1)).min(1),
|
|
321
|
+
default: z3.string().optional()
|
|
92
322
|
}).strict(),
|
|
93
|
-
|
|
94
|
-
type:
|
|
323
|
+
z3.object({
|
|
324
|
+
type: z3.literal("project"),
|
|
95
325
|
...settingsBaseFields,
|
|
96
|
-
default:
|
|
326
|
+
default: z3.string().optional()
|
|
97
327
|
}).strict()
|
|
98
328
|
]);
|
|
99
329
|
function registerSettingDescriptors(target, added) {
|
|
@@ -347,7 +577,7 @@ function requireNonBlankString(args) {
|
|
|
347
577
|
function validateProviderStrings(providerId, value) {
|
|
348
578
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
349
579
|
throw new Error(
|
|
350
|
-
`provider "${providerId}"
|
|
580
|
+
`provider "${providerId}" strings must be an object`
|
|
351
581
|
);
|
|
352
582
|
}
|
|
353
583
|
const record = Object.fromEntries(
|
|
@@ -355,12 +585,12 @@ function validateProviderStrings(providerId, value) {
|
|
|
355
585
|
);
|
|
356
586
|
const required = (field) => requireNonBlankString({
|
|
357
587
|
providerId,
|
|
358
|
-
field: `
|
|
588
|
+
field: `strings.${field}`,
|
|
359
589
|
value: record[field]
|
|
360
590
|
});
|
|
361
591
|
const optional = (field) => record[field] === void 0 ? void 0 : requireNonBlankString({
|
|
362
592
|
providerId,
|
|
363
|
-
field: `
|
|
593
|
+
field: `strings.${field}`,
|
|
364
594
|
value: record[field]
|
|
365
595
|
});
|
|
366
596
|
let iconTint;
|
|
@@ -368,7 +598,7 @@ function validateProviderStrings(providerId, value) {
|
|
|
368
598
|
const tint = record.iconTint;
|
|
369
599
|
if (typeof tint !== "object" || tint === null || Array.isArray(tint)) {
|
|
370
600
|
throw new Error(
|
|
371
|
-
`provider "${providerId}"
|
|
601
|
+
`provider "${providerId}" strings.iconTint must be { light, dark }`
|
|
372
602
|
);
|
|
373
603
|
}
|
|
374
604
|
const tintRecord = Object.fromEntries(
|
|
@@ -377,12 +607,12 @@ function validateProviderStrings(providerId, value) {
|
|
|
377
607
|
iconTint = Object.freeze({
|
|
378
608
|
light: requireNonBlankString({
|
|
379
609
|
providerId,
|
|
380
|
-
field: "
|
|
610
|
+
field: "strings.iconTint.light",
|
|
381
611
|
value: tintRecord.light
|
|
382
612
|
}),
|
|
383
613
|
dark: requireNonBlankString({
|
|
384
614
|
providerId,
|
|
385
|
-
field: "
|
|
615
|
+
field: "strings.iconTint.dark",
|
|
386
616
|
value: tintRecord.dark
|
|
387
617
|
})
|
|
388
618
|
});
|
|
@@ -449,42 +679,42 @@ function validateProviderOptionDescriptors(args) {
|
|
|
449
679
|
function validateProviderExtensionKinds(providerId, value) {
|
|
450
680
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
451
681
|
throw new Error(
|
|
452
|
-
`provider "${providerId}"
|
|
682
|
+
`provider "${providerId}" extensionKinds must be an object keyed by kind name`
|
|
453
683
|
);
|
|
454
684
|
}
|
|
455
685
|
const entries = Object.entries(value);
|
|
456
686
|
if (entries.length > PROVIDER_EXTENSION_KINDS_MAX) {
|
|
457
687
|
throw new Error(
|
|
458
|
-
`provider "${providerId}"
|
|
688
|
+
`provider "${providerId}" extensionKinds declares more than ${PROVIDER_EXTENSION_KINDS_MAX} kinds`
|
|
459
689
|
);
|
|
460
690
|
}
|
|
461
691
|
const normalized = {};
|
|
462
692
|
for (const [name, declaration] of entries) {
|
|
463
693
|
if (!PROVIDER_EXTENSION_KIND_NAME_PATTERN.test(name)) {
|
|
464
694
|
throw new Error(
|
|
465
|
-
`provider "${providerId}"
|
|
695
|
+
`provider "${providerId}" extensionKinds name ${JSON.stringify(name)} must match ${PROVIDER_EXTENSION_KIND_NAME_PATTERN}`
|
|
466
696
|
);
|
|
467
697
|
}
|
|
468
698
|
if (typeof declaration !== "object" || declaration === null || Array.isArray(declaration)) {
|
|
469
699
|
throw new Error(
|
|
470
|
-
`provider "${providerId}"
|
|
700
|
+
`provider "${providerId}" extensionKinds.${name} must be { item?, state? }`
|
|
471
701
|
);
|
|
472
702
|
}
|
|
473
703
|
const item = Reflect.get(declaration, "item");
|
|
474
704
|
const state = Reflect.get(declaration, "state");
|
|
475
705
|
if (item === void 0 && state === void 0) {
|
|
476
706
|
throw new Error(
|
|
477
|
-
`provider "${providerId}"
|
|
707
|
+
`provider "${providerId}" extensionKinds.${name} must declare an item schema, a state schema, or both`
|
|
478
708
|
);
|
|
479
709
|
}
|
|
480
710
|
if (item !== void 0 && !isStandardSchema(item)) {
|
|
481
711
|
throw new Error(
|
|
482
|
-
`provider "${providerId}"
|
|
712
|
+
`provider "${providerId}" extensionKinds.${name}.item must be a Standard Schema v1 validator`
|
|
483
713
|
);
|
|
484
714
|
}
|
|
485
715
|
if (state !== void 0 && !isStandardSchema(state)) {
|
|
486
716
|
throw new Error(
|
|
487
|
-
`provider "${providerId}"
|
|
717
|
+
`provider "${providerId}" extensionKinds.${name}.state must be a Standard Schema v1 validator`
|
|
488
718
|
);
|
|
489
719
|
}
|
|
490
720
|
normalized[name] = Object.freeze({
|
|
@@ -500,57 +730,98 @@ var PROVIDER_ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/u;
|
|
|
500
730
|
function validateProviderEnvPassthrough(providerId, value) {
|
|
501
731
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
502
732
|
throw new Error(
|
|
503
|
-
`provider "${providerId}"
|
|
733
|
+
`provider "${providerId}" env must be { passthrough: [...] }`
|
|
504
734
|
);
|
|
505
735
|
}
|
|
506
736
|
const passthrough = Reflect.get(value, "passthrough");
|
|
507
737
|
if (!Array.isArray(passthrough)) {
|
|
508
738
|
throw new Error(
|
|
509
|
-
`provider "${providerId}"
|
|
739
|
+
`provider "${providerId}" env.passthrough must be an array of variable names`
|
|
510
740
|
);
|
|
511
741
|
}
|
|
512
742
|
if (passthrough.length > PROVIDER_ENV_PASSTHROUGH_MAX) {
|
|
513
743
|
throw new Error(
|
|
514
|
-
`provider "${providerId}"
|
|
744
|
+
`provider "${providerId}" env.passthrough names more than ${PROVIDER_ENV_PASSTHROUGH_MAX} variables`
|
|
515
745
|
);
|
|
516
746
|
}
|
|
517
747
|
const seen = /* @__PURE__ */ new Set();
|
|
518
748
|
for (const name of passthrough) {
|
|
519
749
|
if (typeof name !== "string" || !PROVIDER_ENV_NAME_PATTERN.test(name)) {
|
|
520
750
|
throw new Error(
|
|
521
|
-
`provider "${providerId}"
|
|
751
|
+
`provider "${providerId}" env.passthrough entries must match ${PROVIDER_ENV_NAME_PATTERN}`
|
|
522
752
|
);
|
|
523
753
|
}
|
|
524
754
|
if (seen.has(name)) {
|
|
525
755
|
throw new Error(
|
|
526
|
-
`provider "${providerId}"
|
|
756
|
+
`provider "${providerId}" env.passthrough repeats ${JSON.stringify(name)}`
|
|
527
757
|
);
|
|
528
758
|
}
|
|
529
759
|
seen.add(name);
|
|
530
760
|
}
|
|
531
761
|
return Object.freeze([...seen]);
|
|
532
762
|
}
|
|
763
|
+
function validateProviderNativeRoots(providerId, field, value) {
|
|
764
|
+
const input = providerNativeRootsInputSchema.safeParse(value);
|
|
765
|
+
if (!input.success) {
|
|
766
|
+
const issue = input.error.issues[0];
|
|
767
|
+
const where = issue?.path.length ? `.${issue.path.join(".")}` : "";
|
|
768
|
+
throw new Error(
|
|
769
|
+
`provider "${providerId}" ${field}${where} ${issue?.message ?? "is invalid"}`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
const normalized = normalizeProviderNativeRoots(input.data);
|
|
773
|
+
const wire = providerNativeRootsSchema.safeParse(normalized);
|
|
774
|
+
if (!wire.success) {
|
|
775
|
+
const issue = wire.error.issues[0];
|
|
776
|
+
const where = issue?.path.length ? `.${issue.path.join(".")}` : "";
|
|
777
|
+
throw new Error(
|
|
778
|
+
`provider "${providerId}" ${field}${where} ${issue?.message ?? "is invalid"}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
return Object.freeze({
|
|
782
|
+
user: Object.freeze(wire.data.user.map((root) => Object.freeze(root))),
|
|
783
|
+
project: Object.freeze(wire.data.project.map((root) => Object.freeze(root)))
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
var PROVIDER_MODEL_CATALOG_SCOPES = [
|
|
787
|
+
"host",
|
|
788
|
+
"workspace"
|
|
789
|
+
];
|
|
790
|
+
function validateProviderModelCatalogScope(providerId, value) {
|
|
791
|
+
if (value === void 0) {
|
|
792
|
+
return "workspace";
|
|
793
|
+
}
|
|
794
|
+
if (typeof value !== "string" || !PROVIDER_MODEL_CATALOG_SCOPES.includes(value)) {
|
|
795
|
+
throw new Error(
|
|
796
|
+
`provider "${providerId}" models.scope must be one of ${PROVIDER_MODEL_CATALOG_SCOPES.join(", ")}`
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return value;
|
|
800
|
+
}
|
|
533
801
|
function validateProviderFallbackModels(providerId, value) {
|
|
534
802
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
535
803
|
throw new Error(
|
|
536
|
-
`provider "${providerId}"
|
|
804
|
+
`provider "${providerId}" models must be an object`
|
|
537
805
|
);
|
|
538
806
|
}
|
|
539
807
|
const fallback = Reflect.get(value, "fallback");
|
|
808
|
+
if (fallback === void 0) {
|
|
809
|
+
return void 0;
|
|
810
|
+
}
|
|
540
811
|
if (!Array.isArray(fallback)) {
|
|
541
812
|
throw new Error(
|
|
542
|
-
`provider "${providerId}"
|
|
813
|
+
`provider "${providerId}" models.fallback must be an array`
|
|
543
814
|
);
|
|
544
815
|
}
|
|
545
816
|
if (fallback.length > PROVIDER_FALLBACK_MODELS_MAX) {
|
|
546
817
|
throw new Error(
|
|
547
|
-
`provider "${providerId}"
|
|
818
|
+
`provider "${providerId}" models.fallback lists more than ${PROVIDER_FALLBACK_MODELS_MAX} models`
|
|
548
819
|
);
|
|
549
820
|
}
|
|
550
821
|
const seen = /* @__PURE__ */ new Set();
|
|
551
822
|
let defaults = 0;
|
|
552
823
|
const normalized = fallback.map((entry, index) => {
|
|
553
|
-
const field = `
|
|
824
|
+
const field = `models.fallback[${index}]`;
|
|
554
825
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
555
826
|
throw new Error(`provider "${providerId}" ${field} must be an object`);
|
|
556
827
|
}
|
|
@@ -564,7 +835,7 @@ function validateProviderFallbackModels(providerId, value) {
|
|
|
564
835
|
});
|
|
565
836
|
if (seen.has(id)) {
|
|
566
837
|
throw new Error(
|
|
567
|
-
`provider "${providerId}"
|
|
838
|
+
`provider "${providerId}" models.fallback id ${JSON.stringify(id)} is duplicated`
|
|
568
839
|
);
|
|
569
840
|
}
|
|
570
841
|
seen.add(id);
|
|
@@ -640,11 +911,153 @@ function validateProviderFallbackModels(providerId, value) {
|
|
|
640
911
|
});
|
|
641
912
|
if (normalized.length > 0 && defaults !== 1) {
|
|
642
913
|
throw new Error(
|
|
643
|
-
`provider "${providerId}"
|
|
914
|
+
`provider "${providerId}" models.fallback must mark exactly one model isDefault (found ${defaults})`
|
|
644
915
|
);
|
|
645
916
|
}
|
|
646
917
|
return Object.freeze(normalized);
|
|
647
918
|
}
|
|
919
|
+
var AI_SERVICE_KINDS = /* @__PURE__ */ new Set(["inference", "voice"]);
|
|
920
|
+
var SERVER_DIRECT_AI_SERVICE_IDS = Object.freeze([
|
|
921
|
+
"openai",
|
|
922
|
+
"amazon-bedrock",
|
|
923
|
+
"ant-ling",
|
|
924
|
+
"anthropic",
|
|
925
|
+
"azure-openai-responses",
|
|
926
|
+
"baseten",
|
|
927
|
+
"cerebras",
|
|
928
|
+
"cloudflare-ai-gateway",
|
|
929
|
+
"cloudflare-workers-ai",
|
|
930
|
+
"deepseek",
|
|
931
|
+
"fireworks",
|
|
932
|
+
"github-copilot",
|
|
933
|
+
"google",
|
|
934
|
+
"google-vertex",
|
|
935
|
+
"groq",
|
|
936
|
+
"huggingface",
|
|
937
|
+
"kimi-coding",
|
|
938
|
+
"minimax",
|
|
939
|
+
"minimax-cn",
|
|
940
|
+
"mistral",
|
|
941
|
+
"moonshotai",
|
|
942
|
+
"moonshotai-cn",
|
|
943
|
+
"nvidia",
|
|
944
|
+
"openai-codex",
|
|
945
|
+
"opencode",
|
|
946
|
+
"opencode-go",
|
|
947
|
+
"openrouter",
|
|
948
|
+
"qwen-token-plan",
|
|
949
|
+
"qwen-token-plan-cn",
|
|
950
|
+
"radius",
|
|
951
|
+
"together",
|
|
952
|
+
"vercel-ai-gateway",
|
|
953
|
+
"xai",
|
|
954
|
+
"xiaomi",
|
|
955
|
+
"xiaomi-token-plan-ams",
|
|
956
|
+
"xiaomi-token-plan-cn",
|
|
957
|
+
"xiaomi-token-plan-sgp",
|
|
958
|
+
"zai",
|
|
959
|
+
"zai-coding-cn"
|
|
960
|
+
]);
|
|
961
|
+
function validatePluginAiServiceDeclaration(declaration) {
|
|
962
|
+
if (typeof declaration !== "object" || declaration === null) {
|
|
963
|
+
throw new Error("AI service declaration must be an object");
|
|
964
|
+
}
|
|
965
|
+
const id = declaration.id;
|
|
966
|
+
if (typeof id !== "string" || !PROVIDER_ID_PATTERN.test(id)) {
|
|
967
|
+
throw new Error(
|
|
968
|
+
`invalid AI service id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, and "-", starting with a letter or digit`
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
|
|
972
|
+
if (displayName.length === 0 || displayName.length > 64) {
|
|
973
|
+
throw new Error(
|
|
974
|
+
`AI service "${id}" displayName must be 1-64 characters`
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
const kinds = declaration.kinds;
|
|
978
|
+
if (!Array.isArray(kinds) || kinds.length === 0) {
|
|
979
|
+
throw new Error(`AI service "${id}" must declare at least one kind`);
|
|
980
|
+
}
|
|
981
|
+
const seen = /* @__PURE__ */ new Set();
|
|
982
|
+
for (const kind of kinds) {
|
|
983
|
+
if (typeof kind !== "string" || !AI_SERVICE_KINDS.has(kind)) {
|
|
984
|
+
throw new Error(
|
|
985
|
+
`AI service "${id}" kind ${JSON.stringify(kind)} is not one of: ${[...AI_SERVICE_KINDS].join(", ")}`
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
if (seen.has(kind)) {
|
|
989
|
+
throw new Error(`AI service "${id}" declares kind "${kind}" twice`);
|
|
990
|
+
}
|
|
991
|
+
seen.add(kind);
|
|
992
|
+
}
|
|
993
|
+
return Object.freeze({
|
|
994
|
+
id,
|
|
995
|
+
displayName,
|
|
996
|
+
kinds: Object.freeze([...seen])
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
function assertAiServiceRegistrable(args) {
|
|
1000
|
+
if (SERVER_DIRECT_AI_SERVICE_IDS.includes(args.id)) {
|
|
1001
|
+
throw new Error(
|
|
1002
|
+
`AI service id "${args.id}" is reserved: the server serves it directly, so a plugin cannot register it`
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
if (args.hostArtifact !== null) {
|
|
1006
|
+
return { artifact: args.hostArtifact, problem: null };
|
|
1007
|
+
}
|
|
1008
|
+
if (args.hostArtifactProblem !== null) {
|
|
1009
|
+
return { artifact: null, problem: args.hostArtifactProblem };
|
|
1010
|
+
}
|
|
1011
|
+
throw new Error(
|
|
1012
|
+
`AI service "${args.id}" needs a bb.host entry to run on: this plugin declares none`
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
function aiServiceAlreadyRegisteredMessage(id) {
|
|
1016
|
+
return `AI service "${id}" is already registered; a plugin cannot shadow an existing service.`;
|
|
1017
|
+
}
|
|
1018
|
+
function providerAlreadyRegisteredMessage(id) {
|
|
1019
|
+
return `Provider "${id}" is already registered; a plugin cannot shadow an existing provider.`;
|
|
1020
|
+
}
|
|
1021
|
+
var RENAMED_PROVIDER_DECLARATION_FIELDS = Object.freeze({
|
|
1022
|
+
experimental_family: "family",
|
|
1023
|
+
experimental_strings: "strings",
|
|
1024
|
+
experimental_serviceTiers: "serviceTiers",
|
|
1025
|
+
experimental_reasoningLevels: "reasoningLevels",
|
|
1026
|
+
experimental_extensionKinds: "extensionKinds",
|
|
1027
|
+
experimental_models: "models",
|
|
1028
|
+
experimental_env: "env",
|
|
1029
|
+
experimental_deriveProviderOptions: "deriveProviderOptions"
|
|
1030
|
+
});
|
|
1031
|
+
var MOVED_PROVIDER_CAPABILITY_FIELDS = Object.freeze({
|
|
1032
|
+
experimental_providerHealth: "maintenance.health",
|
|
1033
|
+
experimental_providerUsage: "maintenance.usage",
|
|
1034
|
+
experimental_providerInstallation: "maintenance.installation"
|
|
1035
|
+
});
|
|
1036
|
+
var READ_EXPERIMENTAL_PROVIDER_DECLARATION_FIELDS = /* @__PURE__ */ new Set([
|
|
1037
|
+
"experimental_bridgeOptions",
|
|
1038
|
+
"experimental_visibility",
|
|
1039
|
+
"experimental_nativeSkillRoots",
|
|
1040
|
+
"experimental_nativeCommandRoots",
|
|
1041
|
+
"experimental_resolvesNativeRoots"
|
|
1042
|
+
]);
|
|
1043
|
+
var RENAMED_PROVIDER_FIELDS_SDK_VERSION = "0.4.16";
|
|
1044
|
+
function rejectStaleExperimentalFields(args) {
|
|
1045
|
+
for (const key of Object.keys(args.value)) {
|
|
1046
|
+
if (!key.startsWith("experimental_") || args.read.has(key)) {
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
const replacement = Object.hasOwn(args.renamed, key) ? args.renamed[key] : void 0;
|
|
1050
|
+
const field = `${args.scope}${key}`;
|
|
1051
|
+
if (replacement === void 0) {
|
|
1052
|
+
throw new Error(
|
|
1053
|
+
`provider "${args.providerId}": unknown declaration field "${field}"`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
throw new Error(
|
|
1057
|
+
`provider "${args.providerId}": "${field}" was ${args.verb} to "${replacement}" in SDK ${RENAMED_PROVIDER_FIELDS_SDK_VERSION}`
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
648
1061
|
function validatePluginProviderDeclaration(declaration) {
|
|
649
1062
|
if (typeof declaration !== "object" || declaration === null) {
|
|
650
1063
|
throw new Error("provider declaration must be an object");
|
|
@@ -655,10 +1068,18 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
655
1068
|
`invalid provider id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, and "-", starting with a letter or digit`
|
|
656
1069
|
);
|
|
657
1070
|
}
|
|
658
|
-
|
|
1071
|
+
rejectStaleExperimentalFields({
|
|
1072
|
+
providerId: id,
|
|
1073
|
+
value: declaration,
|
|
1074
|
+
scope: "",
|
|
1075
|
+
read: READ_EXPERIMENTAL_PROVIDER_DECLARATION_FIELDS,
|
|
1076
|
+
renamed: RENAMED_PROVIDER_DECLARATION_FIELDS,
|
|
1077
|
+
verb: "renamed"
|
|
1078
|
+
});
|
|
1079
|
+
const family = declaration.family;
|
|
659
1080
|
if (family !== void 0 && (typeof family !== "string" || !PROVIDER_ID_PATTERN.test(family))) {
|
|
660
1081
|
throw new Error(
|
|
661
|
-
`provider "${id}"
|
|
1082
|
+
`provider "${id}" family must use the provider id grammar (2-64 lowercase letters, digits, and "-")`
|
|
662
1083
|
);
|
|
663
1084
|
}
|
|
664
1085
|
const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
|
|
@@ -671,14 +1092,16 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
671
1092
|
if (declaration.icon !== void 0) {
|
|
672
1093
|
if (typeof declaration.icon !== "string" || declaration.icon.trim() === "") {
|
|
673
1094
|
throw new Error(
|
|
674
|
-
`provider "${id}" icon must be a non-blank string \u2014 a named host glyph ("Zap")
|
|
1095
|
+
`provider "${id}" icon must be a non-blank string \u2014 a named host glyph ("Zap"), a plugin-relative path ("./icons/agent.svg"), or a declared icon ("<pluginId>/<name>")`
|
|
675
1096
|
);
|
|
676
1097
|
}
|
|
677
1098
|
if (isPluginOwnedIconPath(declaration.icon)) {
|
|
678
1099
|
icon = validateProviderRelativePath(declaration.icon, `"${id}" icon`);
|
|
1100
|
+
} else if (isNamespacedGlyph(declaration.icon)) {
|
|
1101
|
+
icon = declaration.icon;
|
|
679
1102
|
} else if (/[/\\]/u.test(declaration.icon)) {
|
|
680
1103
|
throw new Error(
|
|
681
|
-
`provider "${id}" icon looks like a path but does not start with "./" \u2014 use "./icons/agent.svg" for a plugin file, or a bare host glyph name like "Zap"`
|
|
1104
|
+
`provider "${id}" icon looks like a path but does not start with "./" \u2014 use "./icons/agent.svg" for a plugin file, "<pluginId>/<name>" for a declared icon, or a bare host glyph name like "Zap"`
|
|
682
1105
|
);
|
|
683
1106
|
} else {
|
|
684
1107
|
icon = declaration.icon;
|
|
@@ -688,24 +1111,29 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
688
1111
|
if (typeof capabilities !== "object" || capabilities === null) {
|
|
689
1112
|
throw new Error(`provider "${id}" capabilities must be an object`);
|
|
690
1113
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
);
|
|
1114
|
+
rejectStaleExperimentalFields({
|
|
1115
|
+
providerId: id,
|
|
1116
|
+
value: capabilities,
|
|
1117
|
+
scope: "capabilities.",
|
|
1118
|
+
read: /* @__PURE__ */ new Set(),
|
|
1119
|
+
renamed: MOVED_PROVIDER_CAPABILITY_FIELDS,
|
|
1120
|
+
verb: "moved"
|
|
1121
|
+
});
|
|
1122
|
+
const maintenance = declaration.maintenance ?? {};
|
|
1123
|
+
if (typeof maintenance !== "object" || maintenance === null) {
|
|
1124
|
+
throw new Error(`provider "${id}" maintenance must be an object`);
|
|
703
1125
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1126
|
+
for (const key of ["health", "usage", "installation"]) {
|
|
1127
|
+
const value = maintenance[key];
|
|
1128
|
+
if (value !== void 0 && typeof value !== "boolean") {
|
|
1129
|
+
throw new Error(`provider "${id}" maintenance.${key} must be a boolean`);
|
|
1130
|
+
}
|
|
708
1131
|
}
|
|
1132
|
+
const normalizedMaintenance = Object.freeze({
|
|
1133
|
+
health: maintenance.health ?? false,
|
|
1134
|
+
usage: maintenance.usage ?? false,
|
|
1135
|
+
installation: maintenance.installation ?? false
|
|
1136
|
+
});
|
|
709
1137
|
const booleanCapabilityFields = [
|
|
710
1138
|
"supportsServiceTier",
|
|
711
1139
|
"supportsNativeUserQuestion",
|
|
@@ -726,9 +1154,6 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
726
1154
|
);
|
|
727
1155
|
}
|
|
728
1156
|
const normalizedCapabilities = Object.freeze({
|
|
729
|
-
experimental_providerHealth: experimentalProviderHealth,
|
|
730
|
-
experimental_providerUsage: experimentalProviderUsage,
|
|
731
|
-
experimental_providerInstallation: experimentalProviderInstallation,
|
|
732
1157
|
supportsServiceTier: capabilities.supportsServiceTier,
|
|
733
1158
|
supportsNativeUserQuestion: capabilities.supportsNativeUserQuestion,
|
|
734
1159
|
fork: capabilities.fork,
|
|
@@ -767,50 +1192,77 @@ function validatePluginProviderDeclaration(declaration) {
|
|
|
767
1192
|
`provider "${id}" experimental_visibility must be "always" or "installed"`
|
|
768
1193
|
);
|
|
769
1194
|
}
|
|
770
|
-
if (visibility === "installed" && !
|
|
1195
|
+
if (visibility === "installed" && !normalizedMaintenance.health) {
|
|
771
1196
|
throw new Error(
|
|
772
|
-
`provider "${id}" experimental_visibility "installed" requires
|
|
1197
|
+
`provider "${id}" experimental_visibility "installed" requires maintenance.health`
|
|
773
1198
|
);
|
|
774
1199
|
}
|
|
775
|
-
const strings = declaration.
|
|
776
|
-
const serviceTiers = declaration.
|
|
1200
|
+
const strings = declaration.strings === void 0 ? void 0 : validateProviderStrings(id, declaration.strings);
|
|
1201
|
+
const serviceTiers = declaration.serviceTiers === void 0 ? void 0 : validateProviderOptionDescriptors({
|
|
777
1202
|
providerId: id,
|
|
778
|
-
field: "
|
|
779
|
-
value: declaration.
|
|
1203
|
+
field: "serviceTiers",
|
|
1204
|
+
value: declaration.serviceTiers
|
|
780
1205
|
});
|
|
781
|
-
const reasoningLevels = declaration.
|
|
1206
|
+
const reasoningLevels = declaration.reasoningLevels === void 0 ? void 0 : validateProviderOptionDescriptors({
|
|
782
1207
|
providerId: id,
|
|
783
|
-
field: "
|
|
784
|
-
value: declaration.
|
|
1208
|
+
field: "reasoningLevels",
|
|
1209
|
+
value: declaration.reasoningLevels
|
|
785
1210
|
});
|
|
786
|
-
const extensionKinds = declaration.
|
|
1211
|
+
const extensionKinds = declaration.extensionKinds === void 0 ? void 0 : validateProviderExtensionKinds(
|
|
1212
|
+
id,
|
|
1213
|
+
declaration.extensionKinds
|
|
1214
|
+
);
|
|
1215
|
+
const fallbackModels = declaration.models === void 0 ? void 0 : validateProviderFallbackModels(id, declaration.models);
|
|
1216
|
+
const modelCatalogScope = validateProviderModelCatalogScope(
|
|
1217
|
+
id,
|
|
1218
|
+
declaration.models?.scope
|
|
1219
|
+
);
|
|
1220
|
+
const envPassthrough = declaration.env === void 0 ? void 0 : validateProviderEnvPassthrough(id, declaration.env);
|
|
1221
|
+
const nativeSkillRoots = declaration.experimental_nativeSkillRoots === void 0 ? void 0 : validateProviderNativeRoots(
|
|
787
1222
|
id,
|
|
788
|
-
|
|
1223
|
+
"experimental_nativeSkillRoots",
|
|
1224
|
+
declaration.experimental_nativeSkillRoots
|
|
789
1225
|
);
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
1226
|
+
const nativeCommandRoots = declaration.experimental_nativeCommandRoots === void 0 ? void 0 : validateProviderNativeRoots(
|
|
1227
|
+
id,
|
|
1228
|
+
"experimental_nativeCommandRoots",
|
|
1229
|
+
declaration.experimental_nativeCommandRoots
|
|
1230
|
+
);
|
|
1231
|
+
const resolvesNativeRoots = declaration.experimental_resolvesNativeRoots;
|
|
1232
|
+
if (resolvesNativeRoots !== void 0 && typeof resolvesNativeRoots !== "boolean") {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
`provider "${id}" experimental_resolvesNativeRoots must be a boolean`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const deriveProviderOptions = declaration.deriveProviderOptions;
|
|
793
1238
|
if (deriveProviderOptions !== void 0 && typeof deriveProviderOptions !== "function") {
|
|
794
1239
|
throw new Error(
|
|
795
|
-
`provider "${id}"
|
|
1240
|
+
`provider "${id}" deriveProviderOptions must be a function (context) => providerOptions`
|
|
796
1241
|
);
|
|
797
1242
|
}
|
|
798
1243
|
return Object.freeze({
|
|
799
1244
|
id,
|
|
800
1245
|
displayName,
|
|
801
|
-
...family === void 0 ? {} : {
|
|
1246
|
+
...family === void 0 ? {} : { family },
|
|
802
1247
|
...icon === void 0 ? {} : { icon },
|
|
803
1248
|
...bridgeOptions === void 0 ? {} : { experimental_bridgeOptions: bridgeOptions },
|
|
804
1249
|
experimental_visibility: visibility,
|
|
1250
|
+
maintenance: normalizedMaintenance,
|
|
805
1251
|
capabilities: normalizedCapabilities,
|
|
806
1252
|
composerActions,
|
|
807
|
-
...strings === void 0 ? {} : {
|
|
808
|
-
...serviceTiers === void 0 ? {} : {
|
|
809
|
-
...reasoningLevels === void 0 ? {} : {
|
|
810
|
-
...extensionKinds === void 0 ? {} : {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1253
|
+
...strings === void 0 ? {} : { strings },
|
|
1254
|
+
...serviceTiers === void 0 ? {} : { serviceTiers },
|
|
1255
|
+
...reasoningLevels === void 0 ? {} : { reasoningLevels },
|
|
1256
|
+
...extensionKinds === void 0 ? {} : { extensionKinds },
|
|
1257
|
+
models: Object.freeze({
|
|
1258
|
+
...fallbackModels === void 0 ? {} : { fallback: fallbackModels },
|
|
1259
|
+
scope: modelCatalogScope
|
|
1260
|
+
}),
|
|
1261
|
+
...envPassthrough === void 0 ? {} : { env: Object.freeze({ passthrough: envPassthrough }) },
|
|
1262
|
+
...nativeSkillRoots === void 0 ? {} : { experimental_nativeSkillRoots: nativeSkillRoots },
|
|
1263
|
+
...nativeCommandRoots === void 0 ? {} : { experimental_nativeCommandRoots: nativeCommandRoots },
|
|
1264
|
+
experimental_resolvesNativeRoots: resolvesNativeRoots ?? false,
|
|
1265
|
+
...deriveProviderOptions === void 0 ? {} : { deriveProviderOptions }
|
|
814
1266
|
});
|
|
815
1267
|
}
|
|
816
1268
|
function isStandardSchema(value) {
|
|
@@ -978,6 +1430,90 @@ function assertNoRecursiveJsonSchemaReferences(schema, subject) {
|
|
|
978
1430
|
}
|
|
979
1431
|
visit(schema);
|
|
980
1432
|
}
|
|
1433
|
+
var RENAMED_AGENT_TOOL_FIELDS = /* @__PURE__ */ new Map([
|
|
1434
|
+
[
|
|
1435
|
+
"experimental_presentation",
|
|
1436
|
+
'"experimental_presentation" was renamed to "presentation" in SDK 0.4.16'
|
|
1437
|
+
],
|
|
1438
|
+
[
|
|
1439
|
+
"experimental_statusLabels",
|
|
1440
|
+
'"experimental_statusLabels" was folded into "presentation" (labels) in SDK 0.4.16'
|
|
1441
|
+
]
|
|
1442
|
+
]);
|
|
1443
|
+
function rejectStaleAgentToolFields(toolName, tool) {
|
|
1444
|
+
const unknownKeys = [];
|
|
1445
|
+
for (const key of Object.keys(tool).sort()) {
|
|
1446
|
+
const renamed = RENAMED_AGENT_TOOL_FIELDS.get(key);
|
|
1447
|
+
if (renamed !== void 0) {
|
|
1448
|
+
throw new Error(`registerTool: ${renamed} (tool "${toolName}")`);
|
|
1449
|
+
}
|
|
1450
|
+
if (key.startsWith("experimental_")) {
|
|
1451
|
+
unknownKeys.push(key);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
if (unknownKeys.length > 0) {
|
|
1455
|
+
throw new Error(
|
|
1456
|
+
`registerTool: tool "${toolName}" contains unknown field${unknownKeys.length === 1 ? "" : "s"}: ${unknownKeys.join(", ")}`
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
function parsePluginAgentToolPresentation(toolName, value) {
|
|
1461
|
+
if (value === void 0) {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1465
|
+
throw new Error(
|
|
1466
|
+
`tool "${toolName}" presentation must be an object`
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
const declared = value;
|
|
1470
|
+
const presentation = {};
|
|
1471
|
+
if (declared.label !== void 0) {
|
|
1472
|
+
const label = declared.label;
|
|
1473
|
+
if (typeof label !== "object" || label === null || typeof label.pending !== "string" || typeof label.completed !== "string") {
|
|
1474
|
+
throw new Error(
|
|
1475
|
+
`tool "${toolName}" presentation.label must provide pending and completed strings`
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
const { pending, completed } = label;
|
|
1479
|
+
if (pending.trim().length === 0 || completed.trim().length === 0 || pending.length > PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS || completed.length > PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS) {
|
|
1480
|
+
throw new Error(
|
|
1481
|
+
`tool "${toolName}" presentation.label strings must be non-empty and at most ${PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS} characters`
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
presentation.label = { pending, completed };
|
|
1485
|
+
}
|
|
1486
|
+
if (declared.icon !== void 0) {
|
|
1487
|
+
const icon = declared.icon;
|
|
1488
|
+
if (typeof icon !== "object" || icon === null || typeof icon.glyph !== "string" || icon.glyph.trim().length === 0) {
|
|
1489
|
+
throw new Error(
|
|
1490
|
+
`tool "${toolName}" presentation.icon must be { glyph: string }`
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
presentation.icon = { glyph: icon.glyph };
|
|
1494
|
+
}
|
|
1495
|
+
if (declared.suppress !== void 0) {
|
|
1496
|
+
if (typeof declared.suppress !== "boolean") {
|
|
1497
|
+
throw new Error(
|
|
1498
|
+
`tool "${toolName}" presentation.suppress must be a boolean`
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
presentation.suppress = declared.suppress;
|
|
1502
|
+
}
|
|
1503
|
+
if (declared.tint !== void 0) {
|
|
1504
|
+
const tint = declared.tint;
|
|
1505
|
+
if (typeof tint !== "object" || tint === null || typeof tint.light !== "string" || typeof tint.dark !== "string") {
|
|
1506
|
+
throw new Error(
|
|
1507
|
+
`tool "${toolName}" presentation.tint must provide light and dark strings`
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
presentation.tint = {
|
|
1511
|
+
light: tint.light,
|
|
1512
|
+
dark: tint.dark
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
return presentation;
|
|
1516
|
+
}
|
|
981
1517
|
function summarizeParseIssues(error) {
|
|
982
1518
|
const issues = error?.issues;
|
|
983
1519
|
if (Array.isArray(issues) && issues.length > 0) {
|
|
@@ -1058,6 +1594,25 @@ function isResponseLike(value) {
|
|
|
1058
1594
|
const candidate = value;
|
|
1059
1595
|
return typeof candidate.status === "number" && typeof candidate.headers === "object" && candidate.headers !== null && typeof candidate.arrayBuffer === "function" && typeof candidate.clone === "function";
|
|
1060
1596
|
}
|
|
1597
|
+
function undeclaredIconProblem(pluginId, declaredIconNames, glyph) {
|
|
1598
|
+
const parsed = parseNamespacedGlyph(glyph);
|
|
1599
|
+
if (parsed === null) {
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
if (parsed.pluginId !== pluginId || !declaredIconNames.has(parsed.name)) {
|
|
1603
|
+
return `"${glyph}" is not an icon declared by plugin "${pluginId}"`;
|
|
1604
|
+
}
|
|
1605
|
+
return null;
|
|
1606
|
+
}
|
|
1607
|
+
function providerIconRefusalMessage(providerId, problem) {
|
|
1608
|
+
return `provider "${providerId}" icon ${problem}`;
|
|
1609
|
+
}
|
|
1610
|
+
function agentToolIconRefusalMessage(toolName, problem) {
|
|
1611
|
+
return `tool "${toolName}" presentation.icon ${problem}`;
|
|
1612
|
+
}
|
|
1613
|
+
function providerWithoutBridgeMessage(providerId) {
|
|
1614
|
+
return `provider "${providerId}" has no bridge to run on: this plugin declares no "bb.host" entry in its manifest`;
|
|
1615
|
+
}
|
|
1061
1616
|
|
|
1062
1617
|
// src/testing/fake-sdk.ts
|
|
1063
1618
|
function withSpawnAttribution(pluginId, args) {
|
|
@@ -1442,6 +1997,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1442
1997
|
)
|
|
1443
1998
|
};
|
|
1444
1999
|
const pluginId = options.pluginId ?? "test-plugin";
|
|
2000
|
+
const declaredIconNames = new Set(options.experimental_declaredIconNames ?? []);
|
|
1445
2001
|
const agentSkillIds = [...options.agentSkillIds ?? []];
|
|
1446
2002
|
if (new Set(agentSkillIds).size !== agentSkillIds.length) {
|
|
1447
2003
|
throw new Error("agentSkillIds must not contain duplicates");
|
|
@@ -1753,10 +2309,15 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1753
2309
|
function registerProviderDeclaration(declaration) {
|
|
1754
2310
|
assertLive();
|
|
1755
2311
|
const normalized = validatePluginProviderDeclaration(declaration);
|
|
2312
|
+
const iconProblem = normalized.icon === void 0 ? null : undeclaredIconProblem(pluginId, declaredIconNames, normalized.icon);
|
|
2313
|
+
if (iconProblem !== null) {
|
|
2314
|
+
throw new Error(providerIconRefusalMessage(normalized.id, iconProblem));
|
|
2315
|
+
}
|
|
2316
|
+
if (options.experimental_hostEntry === false) {
|
|
2317
|
+
throw new Error(providerWithoutBridgeMessage(normalized.id));
|
|
2318
|
+
}
|
|
1756
2319
|
if (providerRegistrations.some((existing) => existing.id === normalized.id)) {
|
|
1757
|
-
throw new Error(
|
|
1758
|
-
`Provider "${normalized.id}" is already registered; a plugin cannot shadow an existing provider.`
|
|
1759
|
-
);
|
|
2320
|
+
throw new Error(providerAlreadyRegisteredMessage(normalized.id));
|
|
1760
2321
|
}
|
|
1761
2322
|
providerRegistrations.push(normalized);
|
|
1762
2323
|
let disposed2 = false;
|
|
@@ -1769,6 +2330,31 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1769
2330
|
disposeHooks.push(dispose);
|
|
1770
2331
|
return { dispose };
|
|
1771
2332
|
}
|
|
2333
|
+
const aiServiceRegistrations = [];
|
|
2334
|
+
const experimental_aiServices = {
|
|
2335
|
+
register(declaration) {
|
|
2336
|
+
assertLive();
|
|
2337
|
+
const normalized = validatePluginAiServiceDeclaration(declaration);
|
|
2338
|
+
assertAiServiceRegistrable({
|
|
2339
|
+
id: normalized.id,
|
|
2340
|
+
hostArtifact: options.experimental_hostEntry === false ? null : "declared",
|
|
2341
|
+
hostArtifactProblem: null
|
|
2342
|
+
});
|
|
2343
|
+
if (aiServiceRegistrations.some((existing) => existing.id === normalized.id)) {
|
|
2344
|
+
throw new Error(aiServiceAlreadyRegisteredMessage(normalized.id));
|
|
2345
|
+
}
|
|
2346
|
+
aiServiceRegistrations.push(normalized);
|
|
2347
|
+
let disposed2 = false;
|
|
2348
|
+
const dispose = () => {
|
|
2349
|
+
if (disposed2) return;
|
|
2350
|
+
disposed2 = true;
|
|
2351
|
+
const index = aiServiceRegistrations.indexOf(normalized);
|
|
2352
|
+
if (index !== -1) aiServiceRegistrations.splice(index, 1);
|
|
2353
|
+
};
|
|
2354
|
+
disposeHooks.push(dispose);
|
|
2355
|
+
return { dispose };
|
|
2356
|
+
}
|
|
2357
|
+
};
|
|
1772
2358
|
const agents = {
|
|
1773
2359
|
configure(provider) {
|
|
1774
2360
|
assertLive();
|
|
@@ -1794,9 +2380,6 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1794
2380
|
}
|
|
1795
2381
|
instructionProvider = provider;
|
|
1796
2382
|
},
|
|
1797
|
-
experimental_registerProvider(declaration) {
|
|
1798
|
-
return registerProviderDeclaration(declaration);
|
|
1799
|
-
},
|
|
1800
2383
|
registerTool(tool) {
|
|
1801
2384
|
assertLive();
|
|
1802
2385
|
const name = tool?.name;
|
|
@@ -1810,6 +2393,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1810
2393
|
`tool name "${name}" is a built-in bb tool \u2014 pick another name`
|
|
1811
2394
|
);
|
|
1812
2395
|
}
|
|
2396
|
+
rejectStaleAgentToolFields(name, tool);
|
|
1813
2397
|
if (typeof tool.description !== "string" || tool.description.trim().length === 0) {
|
|
1814
2398
|
throw new Error(`tool "${name}" must provide a description`);
|
|
1815
2399
|
}
|
|
@@ -1821,16 +2405,19 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1821
2405
|
`tool "${name}" instructions exceed the ${PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS}-character limit`
|
|
1822
2406
|
);
|
|
1823
2407
|
}
|
|
1824
|
-
const
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2408
|
+
const presentation = parsePluginAgentToolPresentation(
|
|
2409
|
+
name,
|
|
2410
|
+
tool.presentation
|
|
2411
|
+
);
|
|
2412
|
+
if (presentation?.icon !== void 0) {
|
|
2413
|
+
const problem = undeclaredIconProblem(
|
|
2414
|
+
pluginId,
|
|
2415
|
+
declaredIconNames,
|
|
2416
|
+
presentation.icon.glyph
|
|
1833
2417
|
);
|
|
2418
|
+
if (problem !== null) {
|
|
2419
|
+
throw new Error(agentToolIconRefusalMessage(name, problem));
|
|
2420
|
+
}
|
|
1834
2421
|
}
|
|
1835
2422
|
if (typeof tool.execute !== "function") {
|
|
1836
2423
|
throw new Error(
|
|
@@ -1842,7 +2429,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1842
2429
|
let parse;
|
|
1843
2430
|
if (isZodSchemaLike(parameters)) {
|
|
1844
2431
|
try {
|
|
1845
|
-
inputSchema =
|
|
2432
|
+
inputSchema = z4.toJSONSchema(parameters, {
|
|
1846
2433
|
io: "input"
|
|
1847
2434
|
});
|
|
1848
2435
|
} catch (error) {
|
|
@@ -1876,10 +2463,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1876
2463
|
const record = {
|
|
1877
2464
|
name,
|
|
1878
2465
|
description: tool.description,
|
|
1879
|
-
|
|
1880
|
-
pending: experimentalStatusLabels.pending,
|
|
1881
|
-
completed: experimentalStatusLabels.completed
|
|
1882
|
-
},
|
|
2466
|
+
presentation,
|
|
1883
2467
|
instructions: tool.instructions !== void 0 && tool.instructions.trim().length > 0 ? tool.instructions : null,
|
|
1884
2468
|
inputSchema,
|
|
1885
2469
|
parse,
|
|
@@ -1937,10 +2521,15 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
1937
2521
|
}
|
|
1938
2522
|
};
|
|
1939
2523
|
const loopbackBaseUrl = options.loopbackBaseUrl ?? "http://127.0.0.1:38886";
|
|
2524
|
+
const dataDir = options.dataDir ?? "/tmp/bb-fake-data-dir";
|
|
1940
2525
|
const server = {
|
|
1941
2526
|
get loopbackBaseUrl() {
|
|
1942
2527
|
assertLive();
|
|
1943
2528
|
return loopbackBaseUrl;
|
|
2529
|
+
},
|
|
2530
|
+
get experimental_dataDir() {
|
|
2531
|
+
assertLive();
|
|
2532
|
+
return dataDir;
|
|
1944
2533
|
}
|
|
1945
2534
|
};
|
|
1946
2535
|
const { sdk, harness: sdkHarness } = createFakeSdk({
|
|
@@ -2004,24 +2593,24 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2004
2593
|
timeoutMs
|
|
2005
2594
|
};
|
|
2006
2595
|
const id = `fake-interaction-${nextInteractionId++}`;
|
|
2007
|
-
return new Promise((
|
|
2596
|
+
return new Promise((resolve2) => {
|
|
2008
2597
|
const settleAborted = () => {
|
|
2009
2598
|
const pending = pendingInteractions.get(id);
|
|
2010
2599
|
if (!pending) return;
|
|
2011
2600
|
clearTimeout(pending.timer);
|
|
2012
2601
|
pendingInteractions.delete(id);
|
|
2013
|
-
|
|
2602
|
+
resolve2({ outcome: "cancelled", reason: "request-aborted" });
|
|
2014
2603
|
};
|
|
2015
2604
|
requestOptions?.signal?.addEventListener("abort", settleAborted, {
|
|
2016
2605
|
once: true
|
|
2017
2606
|
});
|
|
2018
2607
|
const timer = setTimeout(() => {
|
|
2019
2608
|
pendingInteractions.delete(id);
|
|
2020
|
-
|
|
2609
|
+
resolve2({ outcome: "cancelled", reason: "timeout" });
|
|
2021
2610
|
}, timeoutMs);
|
|
2022
2611
|
pendingInteractions.set(id, {
|
|
2023
2612
|
request: normalizedRequest,
|
|
2024
|
-
resolve,
|
|
2613
|
+
resolve: resolve2,
|
|
2025
2614
|
timer
|
|
2026
2615
|
});
|
|
2027
2616
|
});
|
|
@@ -2188,6 +2777,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2188
2777
|
status,
|
|
2189
2778
|
server,
|
|
2190
2779
|
hosts,
|
|
2780
|
+
experimental_aiServices,
|
|
2191
2781
|
get sdk() {
|
|
2192
2782
|
assertLive();
|
|
2193
2783
|
return sdk;
|
|
@@ -2273,7 +2863,8 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2273
2863
|
};
|
|
2274
2864
|
},
|
|
2275
2865
|
mentionProviders,
|
|
2276
|
-
providerRegistrations
|
|
2866
|
+
providerRegistrations,
|
|
2867
|
+
aiServiceRegistrations
|
|
2277
2868
|
},
|
|
2278
2869
|
get pendingInteractions() {
|
|
2279
2870
|
return [...pendingInteractions].map(([id, pending]) => ({
|
|
@@ -2580,9 +3171,106 @@ function makeThreadResponse(overrides = {}) {
|
|
|
2580
3171
|
...overrides
|
|
2581
3172
|
};
|
|
2582
3173
|
}
|
|
3174
|
+
|
|
3175
|
+
// src/testing/public-sdk-only.ts
|
|
3176
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3177
|
+
import { dirname, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
|
|
3178
|
+
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist"]);
|
|
3179
|
+
var SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]s|tsx)$/u;
|
|
3180
|
+
var TEST_FILE_PATTERN = /\.test\.[cm]?[jt]sx?$/u;
|
|
3181
|
+
var PRIVATE_PACKAGE_PREFIX = "@bb/";
|
|
3182
|
+
var PLUGIN_IMPORT_ALLOWLIST = [
|
|
3183
|
+
/^@get-bb\/plugin-sdk$/u,
|
|
3184
|
+
/^@get-bb\/plugin-sdk\/(?:host|app|ai-services|provider-bridge|provider-bridge\/acp)$/u,
|
|
3185
|
+
/^zod$/u,
|
|
3186
|
+
/^node:/u,
|
|
3187
|
+
/^\.\.?\//u
|
|
3188
|
+
];
|
|
3189
|
+
var TEST_IMPORT_ALLOWLIST = [
|
|
3190
|
+
/^@get-bb\/plugin-sdk\/(?:testing|testing\/app|testing\/host|provider-bridge\/testing)$/u,
|
|
3191
|
+
/^vitest$/u
|
|
3192
|
+
];
|
|
3193
|
+
var IMPORT_SPECIFIER_PATTERN = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']([^"']+)["']/gu;
|
|
3194
|
+
var DYNAMIC_SPECIFIER_PATTERN = /\b(?:import|require)\s*\(\s*(?!["'])([^)]+)\)/gu;
|
|
3195
|
+
var RELATIVE_SPECIFIER_PATTERN = /^\.\.?\//u;
|
|
3196
|
+
function listSourceFiles(directory) {
|
|
3197
|
+
const files = [];
|
|
3198
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
3199
|
+
if (entry.isDirectory()) {
|
|
3200
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name)) {
|
|
3201
|
+
files.push(...listSourceFiles(join2(directory, entry.name)));
|
|
3202
|
+
}
|
|
3203
|
+
continue;
|
|
3204
|
+
}
|
|
3205
|
+
if (SOURCE_EXTENSIONS.test(entry.name)) {
|
|
3206
|
+
files.push(join2(directory, entry.name));
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
return files;
|
|
3210
|
+
}
|
|
3211
|
+
function importSpecifiers(source) {
|
|
3212
|
+
return [...source.matchAll(IMPORT_SPECIFIER_PATTERN)].map(
|
|
3213
|
+
(match) => match[1] ?? ""
|
|
3214
|
+
);
|
|
3215
|
+
}
|
|
3216
|
+
function dynamicSpecifiers(source) {
|
|
3217
|
+
return [...source.matchAll(DYNAMIC_SPECIFIER_PATTERN)].map(
|
|
3218
|
+
(match) => (match[1] ?? "").trim()
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
function escapesPackage(packageRoot, file, specifier) {
|
|
3222
|
+
const target = relative(packageRoot, resolve(dirname(file), specifier));
|
|
3223
|
+
return isAbsolute(target) || target === ".." || target.startsWith(`..${sep}`);
|
|
3224
|
+
}
|
|
3225
|
+
function declaredDependencyNames(packageRoot) {
|
|
3226
|
+
const manifest = JSON.parse(
|
|
3227
|
+
readFileSync(join2(packageRoot, "package.json"), "utf8")
|
|
3228
|
+
);
|
|
3229
|
+
const names = [];
|
|
3230
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
3231
|
+
const block = typeof manifest === "object" && manifest !== null && field in manifest ? manifest[field] : void 0;
|
|
3232
|
+
if (typeof block === "object" && block !== null) {
|
|
3233
|
+
names.push(...Object.keys(block));
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
return names;
|
|
3237
|
+
}
|
|
3238
|
+
function scanPublicSdkOnly(packageRoot, options = {}) {
|
|
3239
|
+
const extra = options.allow ?? [];
|
|
3240
|
+
const pluginAllowlist = [...PLUGIN_IMPORT_ALLOWLIST, ...extra];
|
|
3241
|
+
const testAllowlist = [...pluginAllowlist, ...TEST_IMPORT_ALLOWLIST];
|
|
3242
|
+
const files = [];
|
|
3243
|
+
const violations = [];
|
|
3244
|
+
for (const path of listSourceFiles(packageRoot)) {
|
|
3245
|
+
const file = relative(packageRoot, path);
|
|
3246
|
+
files.push(file);
|
|
3247
|
+
const allowlist = TEST_FILE_PATTERN.test(path) ? testAllowlist : pluginAllowlist;
|
|
3248
|
+
const source = readFileSync(path, "utf8");
|
|
3249
|
+
for (const specifier of importSpecifiers(source)) {
|
|
3250
|
+
if (specifier.startsWith(PRIVATE_PACKAGE_PREFIX)) {
|
|
3251
|
+
violations.push({ file, specifier, reason: "private-package" });
|
|
3252
|
+
} else if (RELATIVE_SPECIFIER_PATTERN.test(specifier) && escapesPackage(packageRoot, path, specifier) && !extra.some((pattern) => pattern.test(specifier))) {
|
|
3253
|
+
violations.push({ file, specifier, reason: "outside-package" });
|
|
3254
|
+
} else if (!allowlist.some((pattern) => pattern.test(specifier))) {
|
|
3255
|
+
violations.push({ file, specifier, reason: "outside-allowlist" });
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
for (const specifier of dynamicSpecifiers(source)) {
|
|
3259
|
+
violations.push({ file, specifier, reason: "dynamic-specifier" });
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
return {
|
|
3263
|
+
files,
|
|
3264
|
+
violations,
|
|
3265
|
+
privateDependencies: declaredDependencyNames(packageRoot).filter(
|
|
3266
|
+
(name) => name.startsWith(PRIVATE_PACKAGE_PREFIX)
|
|
3267
|
+
)
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
2583
3270
|
export {
|
|
2584
3271
|
PluginContextStaleError,
|
|
2585
3272
|
createFakePluginHost,
|
|
2586
3273
|
createFakeSdk,
|
|
3274
|
+
scanPublicSdkOnly as experimental_scanPublicSdkOnly,
|
|
2587
3275
|
makeThreadResponse
|
|
2588
3276
|
};
|