@get-bb/plugin-sdk 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js ADDED
@@ -0,0 +1,36 @@
1
+ // src/app.ts
2
+ var runtime = globalThis.__bbPluginRuntime?.pluginSdkApp ?? {};
3
+ var definePluginApp = runtime.definePluginApp;
4
+ var ThreadChat = runtime.ThreadChat;
5
+ var Markdown = runtime.Markdown;
6
+ var experimental_NewThreadComposer = runtime.experimental_NewThreadComposer;
7
+ var useRpc = runtime.useRpc;
8
+ var useRealtime = runtime.useRealtime;
9
+ var useRealtimeConnectionState = runtime.useRealtimeConnectionState;
10
+ var useSettings = runtime.useSettings;
11
+ var useBbContext = runtime.useBbContext;
12
+ var useBbNavigate = runtime.useBbNavigate;
13
+ var useComposer = runtime.useComposer;
14
+ var useComposerView = runtime.useComposerView;
15
+ var experimental_useSidebarThreads = runtime.experimental_useSidebarThreads;
16
+ var experimental_useSidebarThreadActions = runtime.experimental_useSidebarThreadActions;
17
+ var experimental_useSidebarThreadPullRequest = runtime.experimental_useSidebarThreadPullRequest;
18
+ var experimental_useSidebarThreadSplit = runtime.experimental_useSidebarThreadSplit;
19
+ export {
20
+ Markdown,
21
+ ThreadChat,
22
+ definePluginApp,
23
+ experimental_NewThreadComposer,
24
+ experimental_useSidebarThreadActions,
25
+ experimental_useSidebarThreadPullRequest,
26
+ experimental_useSidebarThreadSplit,
27
+ experimental_useSidebarThreads,
28
+ useBbContext,
29
+ useBbNavigate,
30
+ useComposer,
31
+ useComposerView,
32
+ useRealtime,
33
+ useRealtimeConnectionState,
34
+ useRpc,
35
+ useSettings
36
+ };
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // src/backend-contract.ts
2
+ var PLUGIN_CLI_OUTPUT_MAX_BYTES = 1024 * 1024;
3
+
4
+ // src/rpc-contract.ts
5
+ function defineRpcContract(contract) {
6
+ return contract;
7
+ }
8
+ export {
9
+ PLUGIN_CLI_OUTPUT_MAX_BYTES,
10
+ defineRpcContract
11
+ };
@@ -0,0 +1,238 @@
1
+ // src/internal/composer-customization-validation.ts
2
+ var PLUGIN_SLOT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
3
+ var PLUGIN_MESSAGE_DIRECTIVE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
4
+ function normalizePluginThreadRowStatus(value, onRejected) {
5
+ const kind = "contentScript.experimental_setThreadRowStatus";
6
+ if (value === null) return null;
7
+ if (typeof value !== "object" || Array.isArray(value)) {
8
+ onRejected(`${kind}: status must be null or a non-array object`);
9
+ return void 0;
10
+ }
11
+ const status = value;
12
+ const icon = status.icon;
13
+ if (typeof icon !== "string" || icon.trim() === "") {
14
+ onRejected(`${kind}: "icon" must be a non-blank string`);
15
+ return void 0;
16
+ }
17
+ const label = status.label;
18
+ if (typeof label !== "string" || label.trim() === "") {
19
+ onRejected(`${kind}: "label" must be a non-blank string`);
20
+ return void 0;
21
+ }
22
+ const tone = status.tone;
23
+ if (tone !== void 0 && tone !== "default" && tone !== "running" && tone !== "success" && tone !== "error") {
24
+ onRejected(
25
+ `${kind}: "tone" must be "default", "running", "success", or "error" when set`
26
+ );
27
+ return void 0;
28
+ }
29
+ return {
30
+ icon: icon.trim(),
31
+ label: label.trim(),
32
+ ...tone !== void 0 ? { tone } : {}
33
+ };
34
+ }
35
+ function requireSlotId(kind, value) {
36
+ if (typeof value !== "string" || !PLUGIN_SLOT_ID_PATTERN.test(value)) {
37
+ throw new Error(
38
+ `${kind}: "id" must match ${String(PLUGIN_SLOT_ID_PATTERN)}, got ${JSON.stringify(value)}`
39
+ );
40
+ }
41
+ return value;
42
+ }
43
+ function requireMessageDirectiveId(kind, value) {
44
+ if (typeof value !== "string" || !PLUGIN_MESSAGE_DIRECTIVE_ID_PATTERN.test(value)) {
45
+ throw new Error(
46
+ `${kind}: "id" must match ${String(PLUGIN_MESSAGE_DIRECTIVE_ID_PATTERN)}, got ${JSON.stringify(value)}`
47
+ );
48
+ }
49
+ return value;
50
+ }
51
+ function requireNonEmptyString(kind, field, value) {
52
+ if (typeof value !== "string" || value.length === 0) {
53
+ throw new Error(`${kind}: "${field}" must be a non-empty string`);
54
+ }
55
+ return value;
56
+ }
57
+ function requireOptionalString(kind, field, value) {
58
+ if (value !== void 0 && typeof value !== "string") {
59
+ throw new Error(`${kind}: "${field}" must be a string when set`);
60
+ }
61
+ return value;
62
+ }
63
+ function requireComponent(kind, value) {
64
+ if (typeof value !== "function") {
65
+ throw new Error(`${kind}: "component" must be a React component function`);
66
+ }
67
+ return value;
68
+ }
69
+ function requireFunction(kind, field, value) {
70
+ if (typeof value !== "function") {
71
+ throw new Error(`${kind}: "${field}" must be a function`);
72
+ }
73
+ return value;
74
+ }
75
+ function requireUniqueId(kind, seen, id) {
76
+ if (seen.has(id)) {
77
+ throw new Error(`${kind}: duplicate id "${id}"`);
78
+ }
79
+ seen.add(id);
80
+ }
81
+ function parseContributionArray(kind, value, onRejected, parse) {
82
+ if (value === void 0) return void 0;
83
+ if (!Array.isArray(value)) {
84
+ onRejected(`${kind}: must be an array when set`);
85
+ return void 0;
86
+ }
87
+ const seenIds = /* @__PURE__ */ new Set();
88
+ const parsed = [];
89
+ for (const [index, entry] of value.entries()) {
90
+ const entryKind = `${kind}[${index}]`;
91
+ try {
92
+ const parsedEntry = parse(entryKind, entry);
93
+ requireUniqueId(entryKind, seenIds, parsedEntry.id);
94
+ parsed.push(parsedEntry);
95
+ } catch (error) {
96
+ onRejected(error instanceof Error ? error.message : String(error));
97
+ }
98
+ }
99
+ return parsed;
100
+ }
101
+ function parseRegions(kind, registration, onRejected) {
102
+ const actions = parseContributionArray(`${kind}.actions`, registration.actions, onRejected, (entryKind, value) => {
103
+ const entry = value;
104
+ return {
105
+ id: requireSlotId(entryKind, entry?.id),
106
+ component: requireComponent(entryKind, entry?.component)
107
+ };
108
+ });
109
+ const banners = parseContributionArray(`${kind}.banners`, registration.banners, onRejected, (entryKind, value) => {
110
+ const entry = value;
111
+ const id = requireSlotId(entryKind, entry?.id);
112
+ const chrome = entry?.chrome;
113
+ if (chrome !== void 0 && chrome !== "card" && chrome !== "bare") {
114
+ throw new Error(
115
+ `${entryKind}: "chrome" must be "card" or "bare" when set`
116
+ );
117
+ }
118
+ return {
119
+ id,
120
+ ...chrome !== void 0 ? { chrome } : {},
121
+ component: requireComponent(entryKind, entry?.component)
122
+ };
123
+ });
124
+ const plusMenu = parseContributionArray(
125
+ `${kind}.plusMenu`,
126
+ registration.plusMenu,
127
+ onRejected,
128
+ (entryKind, value) => {
129
+ const entry = value;
130
+ const id = requireSlotId(entryKind, entry?.id);
131
+ const icon = requireOptionalString(entryKind, "icon", entry?.icon);
132
+ const description = requireOptionalString(
133
+ entryKind,
134
+ "description",
135
+ entry?.description
136
+ );
137
+ const disabled = entry?.disabled;
138
+ if (disabled !== void 0 && typeof disabled !== "boolean" && typeof disabled !== "function") {
139
+ throw new Error(
140
+ `${entryKind}: "disabled" must be a boolean or function when set`
141
+ );
142
+ }
143
+ return {
144
+ id,
145
+ label: requireNonEmptyString(entryKind, "label", entry?.label),
146
+ ...icon !== void 0 ? { icon } : {},
147
+ ...description !== void 0 ? { description } : {},
148
+ ...disabled !== void 0 ? {
149
+ disabled
150
+ } : {},
151
+ run: requireFunction(entryKind, "run", entry?.run)
152
+ };
153
+ }
154
+ );
155
+ let richText;
156
+ if (registration.richText !== void 0) {
157
+ const raw = registration.richText;
158
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
159
+ onRejected(`${kind}.richText: must be an object when set`);
160
+ } else {
161
+ const effects = parseContributionArray(
162
+ `${kind}.richText.effects`,
163
+ raw.effects,
164
+ onRejected,
165
+ (entryKind, value) => {
166
+ const entry = value;
167
+ return {
168
+ id: requireSlotId(entryKind, entry?.id),
169
+ match: requireFunction(entryKind, "match", entry?.match),
170
+ className: requireNonEmptyString(
171
+ entryKind,
172
+ "className",
173
+ entry?.className
174
+ )
175
+ };
176
+ }
177
+ );
178
+ const onDraftChange = raw.onDraftChange;
179
+ if (onDraftChange !== void 0 && typeof onDraftChange !== "function") {
180
+ onRejected(
181
+ `${kind}.richText: "onDraftChange" must be a function when set`
182
+ );
183
+ }
184
+ richText = {
185
+ ...effects !== void 0 ? { effects } : {},
186
+ ...typeof onDraftChange === "function" ? {
187
+ onDraftChange
188
+ } : {}
189
+ };
190
+ }
191
+ }
192
+ return {
193
+ ...actions !== void 0 ? { actions } : {},
194
+ ...banners !== void 0 ? { banners } : {},
195
+ ...plusMenu !== void 0 ? { plusMenu } : {},
196
+ ...richText !== void 0 ? { richText } : {}
197
+ };
198
+ }
199
+ function collectComposerCustomization(registration, seenIds, onRejected) {
200
+ const kind = "composer.customize";
201
+ try {
202
+ const raw = registration;
203
+ const id = requireSlotId(kind, raw?.id);
204
+ const scopes = raw?.scopes;
205
+ if (scopes !== void 0) {
206
+ if (!Array.isArray(scopes)) {
207
+ throw new Error(`${kind}: "scopes" must be an array when set`);
208
+ }
209
+ for (const scope of scopes) {
210
+ if (scope !== "thread" && scope !== "queued-message" && scope !== "side-chat" && scope !== "new-thread") {
211
+ throw new Error(
212
+ `${kind}: invalid scope kind ${JSON.stringify(scope)}`
213
+ );
214
+ }
215
+ }
216
+ }
217
+ requireUniqueId(kind, seenIds, id);
218
+ return {
219
+ id,
220
+ ...scopes !== void 0 ? { scopes: [...scopes] } : {},
221
+ ...parseRegions(`${kind}(${id})`, raw ?? {}, onRejected)
222
+ };
223
+ } catch (error) {
224
+ onRejected(error instanceof Error ? error.message : String(error));
225
+ return null;
226
+ }
227
+ }
228
+ export {
229
+ PLUGIN_SLOT_ID_PATTERN,
230
+ collectComposerCustomization,
231
+ normalizePluginThreadRowStatus,
232
+ requireComponent,
233
+ requireMessageDirectiveId,
234
+ requireNonEmptyString,
235
+ requireOptionalString,
236
+ requireSlotId,
237
+ requireUniqueId
238
+ };
@@ -0,0 +1,7 @@
1
+ // src/internal/composer-view.ts
2
+ function isComposerDraftEmpty(text, attachmentCount) {
3
+ return text.trim().length === 0 && attachmentCount === 0;
4
+ }
5
+ export {
6
+ isComposerDraftEmpty
7
+ };
@@ -0,0 +1,261 @@
1
+ // src/internal/host-policy.ts
2
+ import { z } from "zod";
3
+
4
+ // ../domain/src/plugin-cli.ts
5
+ var RESERVED_BB_CLI_COMMANDS = [
6
+ "environment",
7
+ "guide",
8
+ "help",
9
+ "manager",
10
+ "plugin",
11
+ "project",
12
+ "provider",
13
+ "skill",
14
+ "status",
15
+ "theme",
16
+ "thread"
17
+ ];
18
+
19
+ // src/backend-contract.ts
20
+ var PLUGIN_CLI_OUTPUT_MAX_BYTES = 1024 * 1024;
21
+
22
+ // src/internal/host-policy.ts
23
+ var RESERVED_AGENT_TOOL_NAMES = [
24
+ "update_environment_directory"
25
+ ];
26
+ var KV_VALUE_MAX_BYTES = 256 * 1024;
27
+ var PLUGIN_HTTP_METHODS = /* @__PURE__ */ new Set([
28
+ "GET",
29
+ "POST",
30
+ "PUT",
31
+ "PATCH",
32
+ "DELETE",
33
+ "HEAD",
34
+ "OPTIONS"
35
+ ]);
36
+ var RPC_METHOD_PATTERN = /^[a-zA-Z0-9_-]+$/;
37
+ var BACKGROUND_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
38
+ var CLI_COMMAND_NAME_PATTERN = /^[a-z0-9-]+$/;
39
+ var AGENT_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
40
+ var PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS = 4096;
41
+ var PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS = 80;
42
+ var PLUGIN_AGENT_SELECTION_MAX_IDS = 256;
43
+ var PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS = 4096;
44
+ var PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES = 128 * 1024;
45
+ var MENTION_PROVIDER_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
46
+ var SETTING_KEY_PATTERN = /^[a-zA-Z0-9_-]+$/;
47
+ var settingsBaseFields = {
48
+ label: z.string().min(1),
49
+ description: z.string().min(1).optional()
50
+ };
51
+ var settingDescriptorSchema = z.discriminatedUnion("type", [
52
+ z.object({
53
+ type: z.literal("string"),
54
+ ...settingsBaseFields,
55
+ secret: z.literal(true).optional(),
56
+ default: z.string().optional()
57
+ }).strict(),
58
+ z.object({
59
+ type: z.literal("boolean"),
60
+ ...settingsBaseFields,
61
+ default: z.boolean().optional()
62
+ }).strict(),
63
+ z.object({
64
+ type: z.literal("select"),
65
+ ...settingsBaseFields,
66
+ options: z.array(z.string().min(1)).min(1),
67
+ default: z.string().optional()
68
+ }).strict(),
69
+ z.object({
70
+ type: z.literal("project"),
71
+ ...settingsBaseFields,
72
+ default: z.string().optional()
73
+ }).strict()
74
+ ]);
75
+ function registerSettingDescriptors(target, added) {
76
+ const validated = {};
77
+ for (const [key, raw] of Object.entries(added)) {
78
+ if (!SETTING_KEY_PATTERN.test(key)) {
79
+ throw new Error(
80
+ `invalid setting key "${key}" \u2014 use letters, digits, "-" and "_"`
81
+ );
82
+ }
83
+ if (key in target) {
84
+ throw new Error(`setting "${key}" is already defined`);
85
+ }
86
+ const parsed = settingDescriptorSchema.safeParse(raw);
87
+ if (!parsed.success) {
88
+ const issue = parsed.error.issues[0];
89
+ const path = issue?.path.join(".") ?? "";
90
+ throw new Error(
91
+ `invalid descriptor for setting "${key}"${path ? ` (${path})` : ""}: ${issue?.message ?? "unknown error"}`
92
+ );
93
+ }
94
+ const descriptor = parsed.data;
95
+ if (descriptor.type === "select" && descriptor.default !== void 0 && !descriptor.options.includes(descriptor.default)) {
96
+ throw new Error(
97
+ `default for setting "${key}" must be one of its options`
98
+ );
99
+ }
100
+ validated[key] = descriptor;
101
+ }
102
+ Object.assign(target, validated);
103
+ return validated;
104
+ }
105
+ function validateSettingsUpdate(descriptors, values) {
106
+ const errors = [];
107
+ for (const [key, value] of Object.entries(values)) {
108
+ const descriptor = descriptors[key];
109
+ if (!descriptor) {
110
+ errors.push(`unknown setting "${key}"`);
111
+ continue;
112
+ }
113
+ if (value === null) continue;
114
+ if (descriptor.type === "boolean") {
115
+ if (typeof value !== "boolean") {
116
+ errors.push(`setting "${key}" expects a boolean`);
117
+ }
118
+ continue;
119
+ }
120
+ if (typeof value !== "string") {
121
+ errors.push(`setting "${key}" expects a string`);
122
+ continue;
123
+ }
124
+ if (descriptor.type === "select" && !descriptor.options.includes(value)) {
125
+ errors.push(
126
+ `setting "${key}" must be one of: ${descriptor.options.join(", ")}`
127
+ );
128
+ }
129
+ }
130
+ return errors;
131
+ }
132
+ var PLUGIN_MENTION_TRIGGER_VALUES = [
133
+ "@",
134
+ "#",
135
+ "$",
136
+ "!",
137
+ "~"
138
+ ];
139
+ var DEFAULT_PLUGIN_MENTION_TRIGGERS = [
140
+ "@"
141
+ ];
142
+ function isPluginMentionTrigger(value) {
143
+ return typeof value === "string" && PLUGIN_MENTION_TRIGGER_VALUES.includes(value);
144
+ }
145
+ function normalizeMentionProviderTriggers(providerId, triggers) {
146
+ if (triggers === void 0) {
147
+ return DEFAULT_PLUGIN_MENTION_TRIGGERS;
148
+ }
149
+ if (!Array.isArray(triggers)) {
150
+ throw new Error(
151
+ `mention provider "${providerId}" triggers must be an array`
152
+ );
153
+ }
154
+ if (triggers.length === 0) {
155
+ throw new Error(
156
+ `mention provider "${providerId}" triggers must include at least one trigger`
157
+ );
158
+ }
159
+ const seen = /* @__PURE__ */ new Set();
160
+ const normalized = [];
161
+ for (const trigger of triggers) {
162
+ if (!isPluginMentionTrigger(trigger)) {
163
+ throw new Error(
164
+ `mention provider "${providerId}" trigger ${JSON.stringify(trigger)} is invalid; use one of ${PLUGIN_MENTION_TRIGGER_VALUES.join(" ")}`
165
+ );
166
+ }
167
+ if (seen.has(trigger)) {
168
+ throw new Error(
169
+ `mention provider "${providerId}" trigger ${JSON.stringify(trigger)} is duplicated`
170
+ );
171
+ }
172
+ seen.add(trigger);
173
+ normalized.push(trigger);
174
+ }
175
+ return normalized;
176
+ }
177
+ function isStandardSchema(value) {
178
+ if (typeof value !== "object" || value === null) return false;
179
+ const standard = Reflect.get(value, "~standard");
180
+ return typeof standard === "object" && standard !== null && Reflect.get(standard, "version") === 1 && typeof Reflect.get(standard, "vendor") === "string" && typeof Reflect.get(standard, "validate") === "function";
181
+ }
182
+ function readRpcMethodContract(method, value) {
183
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
184
+ throw new Error(
185
+ `rpc method "${method}" contract must provide input and output Standard Schemas`
186
+ );
187
+ }
188
+ const input = Reflect.get(value, "input");
189
+ const output = Reflect.get(value, "output");
190
+ if (!isStandardSchema(input)) {
191
+ throw new Error(
192
+ `rpc method "${method}" input must be a Standard Schema v1 validator`
193
+ );
194
+ }
195
+ if (!isStandardSchema(output)) {
196
+ throw new Error(
197
+ `rpc method "${method}" output must be a Standard Schema v1 validator`
198
+ );
199
+ }
200
+ return { input, output };
201
+ }
202
+ function isZodSchemaLike(value) {
203
+ return typeof value === "object" && value !== null && typeof value.safeParse === "function";
204
+ }
205
+ function summarizeParseIssues(error) {
206
+ const issues = error?.issues;
207
+ if (Array.isArray(issues) && issues.length > 0) {
208
+ return issues.map((issue) => {
209
+ const path = Array.isArray(issue.path) && issue.path.length > 0 ? issue.path.join(".") : "(input)";
210
+ return `${path}: ${issue.message ?? "invalid"}`;
211
+ }).join("; ");
212
+ }
213
+ return error instanceof Error ? error.message : String(error);
214
+ }
215
+ function enforcePluginCliOutputLimit(result, jsonOutput) {
216
+ const stdoutBytes = Buffer.byteLength(result.stdout, "utf8");
217
+ const stderrBytes = Buffer.byteLength(result.stderr, "utf8");
218
+ const totalBytes = stdoutBytes + stderrBytes;
219
+ if (totalBytes <= PLUGIN_CLI_OUTPUT_MAX_BYTES) return result;
220
+ const error = {
221
+ code: "plugin_cli_output_too_large",
222
+ message: `Plugin CLI output is ${totalBytes} bytes (${stdoutBytes} stdout + ${stderrBytes} stderr), exceeding the ${PLUGIN_CLI_OUTPUT_MAX_BYTES}-byte limit. Narrow the query, request a smaller page, or use a file/streaming command.`,
223
+ maxBytes: PLUGIN_CLI_OUTPUT_MAX_BYTES,
224
+ stdoutBytes,
225
+ stderrBytes,
226
+ totalBytes
227
+ };
228
+ return jsonOutput ? {
229
+ exitCode: 1,
230
+ stdout: JSON.stringify({ error }),
231
+ stderr: "",
232
+ error
233
+ } : { exitCode: 1, stdout: "", stderr: error.message, error };
234
+ }
235
+ export {
236
+ AGENT_TOOL_NAME_PATTERN,
237
+ BACKGROUND_NAME_PATTERN,
238
+ CLI_COMMAND_NAME_PATTERN,
239
+ KV_VALUE_MAX_BYTES,
240
+ MENTION_PROVIDER_ID_PATTERN,
241
+ PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS,
242
+ PLUGIN_AGENT_SELECTION_MAX_IDS,
243
+ PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS,
244
+ PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS,
245
+ PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES,
246
+ PLUGIN_HTTP_METHODS,
247
+ PLUGIN_MENTION_TRIGGER_VALUES,
248
+ RESERVED_AGENT_TOOL_NAMES,
249
+ RESERVED_BB_CLI_COMMANDS,
250
+ RPC_METHOD_PATTERN,
251
+ SETTING_KEY_PATTERN,
252
+ enforcePluginCliOutputLimit,
253
+ isPluginMentionTrigger,
254
+ isStandardSchema,
255
+ isZodSchemaLike,
256
+ normalizeMentionProviderTriggers,
257
+ readRpcMethodContract,
258
+ registerSettingDescriptors,
259
+ summarizeParseIssues,
260
+ validateSettingsUpdate
261
+ };