@get-bb/plugin-sdk 0.4.56 → 0.4.87
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 +0 -12
- package/bundled-types/bb-plugin-sdk-app.d.ts +137 -20
- package/bundled-types/bb-plugin-sdk-environment-provider.d.ts +35 -3
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +406 -52
- package/bundled-types/bb-plugin-sdk-internal-plugin-app-collector.d.ts +8 -3
- package/bundled-types/bb-plugin-sdk-machine-provider.d.ts +84 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts +57 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge.d.ts +131 -2
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +7 -2
- package/bundled-types/bb-plugin-sdk-testing.d.ts +212 -12
- package/bundled-types/bb-plugin-sdk.d.ts +749 -49
- package/dist/app.js +4 -0
- package/dist/host.js +1 -1
- package/dist/internal/host-policy.js +855 -68
- package/dist/internal/plugin-app-collector.js +116 -91
- package/dist/machine-provider.js +0 -0
- package/dist/provider-bridge-acp.js +1964 -2045
- package/dist/provider-bridge-testing.js +1378 -1404
- package/dist/provider-bridge-worker-entry.mjs +0 -1
- package/dist/provider-bridge.js +1455 -1498
- package/dist/testing/app.js +134 -91
- package/dist/testing/index.js +1092 -730
- package/package.json +7 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/internal/host-policy.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { z as z4 } from "zod";
|
|
3
4
|
|
|
4
5
|
// ../domain/src/plugin-icon.ts
|
|
@@ -44,6 +45,10 @@ var RESERVED_BB_CLI_COMMANDS = [
|
|
|
44
45
|
"voice"
|
|
45
46
|
];
|
|
46
47
|
|
|
48
|
+
// ../domain/src/plugin-interaction-limits.ts
|
|
49
|
+
var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
|
|
50
|
+
var PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
|
|
51
|
+
|
|
47
52
|
// ../domain/src/provider-fork.ts
|
|
48
53
|
import { z } from "zod";
|
|
49
54
|
var PROVIDER_FORK_VALUES = ["none", "tip", "checkpoint"];
|
|
@@ -306,8 +311,7 @@ var pluginProviderEnvEntrySchema = z4.object({
|
|
|
306
311
|
z4.string(),
|
|
307
312
|
z4.object({ serverPath: z4.string().startsWith("/") }).strict()
|
|
308
313
|
]),
|
|
309
|
-
reason: z4.string()
|
|
310
|
-
secret: z4.boolean()
|
|
314
|
+
reason: z4.string()
|
|
311
315
|
}).strict();
|
|
312
316
|
var pluginProviderEnvEntriesSchema = z4.array(pluginProviderEnvEntrySchema).max(PLUGIN_PROVIDER_ENV_MAX_ENTRIES).superRefine((entries, context) => {
|
|
313
317
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -515,6 +519,22 @@ function validateSettingsUpdate(descriptors, values) {
|
|
|
515
519
|
}
|
|
516
520
|
return errors;
|
|
517
521
|
}
|
|
522
|
+
function coerceStoredPluginSettingValue(descriptor, value) {
|
|
523
|
+
let coerced = value;
|
|
524
|
+
if (descriptor.type === "number" && typeof coerced === "string") {
|
|
525
|
+
const legacyNumber = Number(coerced.trim());
|
|
526
|
+
coerced = coerced.trim().length > 0 && Number.isFinite(legacyNumber) ? legacyNumber : void 0;
|
|
527
|
+
}
|
|
528
|
+
if (descriptor.type === "number" && typeof coerced === "number" && !Number.isFinite(coerced)) {
|
|
529
|
+
coerced = void 0;
|
|
530
|
+
}
|
|
531
|
+
const expected = descriptor.type === "boolean" ? "boolean" : descriptor.type === "number" ? "number" : "string";
|
|
532
|
+
if (typeof coerced !== expected) coerced = void 0;
|
|
533
|
+
if (descriptor.type === "select" && typeof coerced === "string" && !descriptor.options.includes(coerced)) {
|
|
534
|
+
coerced = void 0;
|
|
535
|
+
}
|
|
536
|
+
return coerced ?? descriptor.default;
|
|
537
|
+
}
|
|
518
538
|
var PLUGIN_MENTION_TRIGGER_VALUES = [
|
|
519
539
|
"@",
|
|
520
540
|
"#",
|
|
@@ -1776,79 +1796,64 @@ function pluginHookAlreadyRegisteredMessage(hook) {
|
|
|
1776
1796
|
}
|
|
1777
1797
|
var ENVIRONMENT_PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,63}$/;
|
|
1778
1798
|
var ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS = 80;
|
|
1799
|
+
var ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS = 200;
|
|
1779
1800
|
var ENVIRONMENT_PROVIDER_REQUIREMENT_NAMES = [
|
|
1780
1801
|
"projectCheckout",
|
|
1781
1802
|
"gitCheckout",
|
|
1782
1803
|
"gitRemote",
|
|
1783
1804
|
"projectless"
|
|
1784
1805
|
];
|
|
1785
|
-
|
|
1806
|
+
var environmentCompositionSchema = z4.object({
|
|
1807
|
+
id: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN),
|
|
1808
|
+
displayName: z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS),
|
|
1809
|
+
description: z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS).optional().transform((value) => value ?? null),
|
|
1810
|
+
icon: z4.string().trim().min(1).transform(
|
|
1811
|
+
(icon) => isPluginOwnedIconPath(icon) ? validateProviderRelativePath(icon, "Composition icon") : icon
|
|
1812
|
+
).optional().transform((value) => value ?? null),
|
|
1813
|
+
machineProviderId: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN),
|
|
1814
|
+
environmentProviderId: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN)
|
|
1815
|
+
}).strict();
|
|
1816
|
+
function requireProviderIdAndDisplayName(noun, declaration) {
|
|
1786
1817
|
if (typeof declaration !== "object" || declaration === null) {
|
|
1787
|
-
throw new Error(
|
|
1818
|
+
throw new Error(`${noun} declaration must be an object`);
|
|
1788
1819
|
}
|
|
1789
1820
|
const id = declaration.id;
|
|
1790
1821
|
if (typeof id !== "string" || !ENVIRONMENT_PROVIDER_ID_PATTERN.test(id)) {
|
|
1791
1822
|
throw new Error(
|
|
1792
|
-
`invalid
|
|
1823
|
+
`invalid ${noun} id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, or "-", starting with a letter or digit`
|
|
1793
1824
|
);
|
|
1794
1825
|
}
|
|
1795
1826
|
const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
|
|
1796
1827
|
if (displayName.length === 0 || displayName.length > ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS) {
|
|
1797
1828
|
throw new Error(
|
|
1798
|
-
|
|
1829
|
+
`${noun} "${id}" needs a displayName of 1-${ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS} characters`
|
|
1799
1830
|
);
|
|
1800
1831
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
`environment provider "${id}" icon must be a glyph, declared icon, or plugin-relative path`
|
|
1808
|
-
);
|
|
1809
|
-
}
|
|
1810
|
-
if (icon !== null && icon.length === 0) {
|
|
1811
|
-
throw new Error(`environment provider "${id}" declares an empty icon`);
|
|
1812
|
-
}
|
|
1813
|
-
const requires = normalizeEnvironmentProviderRequirements(id, declaration);
|
|
1814
|
-
const inputs = normalizeEnvironmentProviderInputs(id, declaration);
|
|
1815
|
-
if (typeof declaration.create !== "function" || typeof declaration.remove !== "function") {
|
|
1816
|
-
throw new Error(
|
|
1817
|
-
`environment provider "${id}" must declare create and remove functions`
|
|
1818
|
-
);
|
|
1819
|
-
}
|
|
1820
|
-
if (declaration.validate !== void 0 && typeof declaration.validate !== "function") {
|
|
1832
|
+
return { id, displayName };
|
|
1833
|
+
}
|
|
1834
|
+
function assertProviderIconShape(noun, id, icon) {
|
|
1835
|
+
if (isPluginOwnedIconPath(icon)) {
|
|
1836
|
+
validateProviderRelativePath(icon, `"${id}" icon`);
|
|
1837
|
+
} else if (!isNamespacedGlyph(icon) && /[/\\]/u.test(icon)) {
|
|
1821
1838
|
throw new Error(
|
|
1822
|
-
|
|
1839
|
+
`${noun} "${id}" icon must be a glyph, declared icon, or plugin-relative path`
|
|
1823
1840
|
);
|
|
1824
1841
|
}
|
|
1825
|
-
|
|
1842
|
+
}
|
|
1843
|
+
function assertOptionalFunction(noun, id, value, phrase) {
|
|
1844
|
+
if (value !== void 0 && typeof value !== "function") {
|
|
1826
1845
|
throw new Error(
|
|
1827
|
-
|
|
1846
|
+
`${noun} "${id}" declares ${phrase} that is not a function`
|
|
1828
1847
|
);
|
|
1829
1848
|
}
|
|
1830
|
-
return {
|
|
1831
|
-
id,
|
|
1832
|
-
displayName,
|
|
1833
|
-
icon,
|
|
1834
|
-
requires,
|
|
1835
|
-
inputs: inputs === null ? null : inputs.schema,
|
|
1836
|
-
inputsJsonSchema: inputs === null ? null : inputs.jsonSchema,
|
|
1837
|
-
availability: declaration.availability ?? null,
|
|
1838
|
-
validate: declaration.validate ?? null,
|
|
1839
|
-
create: declaration.create,
|
|
1840
|
-
remove: declaration.remove,
|
|
1841
|
-
policy: environmentProviderPolicySchema.parse(declaration.policy ?? {})
|
|
1842
|
-
};
|
|
1843
1849
|
}
|
|
1844
|
-
function
|
|
1845
|
-
const inputs = declaration.inputs;
|
|
1850
|
+
function normalizeProviderInputs(noun, id, inputs) {
|
|
1846
1851
|
if (inputs === void 0) {
|
|
1847
1852
|
return null;
|
|
1848
1853
|
}
|
|
1849
1854
|
if (!isStandardSchema(inputs)) {
|
|
1850
1855
|
throw new Error(
|
|
1851
|
-
|
|
1856
|
+
`${noun} "${id}" declares an inputs that is not a Standard Schema v1 validator`
|
|
1852
1857
|
);
|
|
1853
1858
|
}
|
|
1854
1859
|
let converted;
|
|
@@ -1856,17 +1861,65 @@ function normalizeEnvironmentProviderInputs(id, declaration) {
|
|
|
1856
1861
|
converted = JSON.parse(JSON.stringify(standardSchemaToJsonSchema(inputs)));
|
|
1857
1862
|
} catch (error) {
|
|
1858
1863
|
throw new Error(
|
|
1859
|
-
|
|
1864
|
+
`${noun} "${id}" declares an inputs validator that cannot be published as JSON Schema (${error instanceof Error ? error.message : String(error)}) \u2014 declare it with zod 4 or a validator exposing toJSONSchema()`
|
|
1860
1865
|
);
|
|
1861
1866
|
}
|
|
1862
1867
|
const jsonSchema = jsonValueSchema.safeParse(converted);
|
|
1863
1868
|
if (!jsonSchema.success) {
|
|
1864
1869
|
throw new Error(
|
|
1865
|
-
|
|
1870
|
+
`${noun} "${id}" declares an inputs schema whose JSON Schema is not JSON-serializable`
|
|
1866
1871
|
);
|
|
1867
1872
|
}
|
|
1868
1873
|
return { schema: inputs, jsonSchema: jsonSchema.data };
|
|
1869
1874
|
}
|
|
1875
|
+
function validatePluginEnvironmentProviderDeclaration(declaration) {
|
|
1876
|
+
const { id, displayName } = requireProviderIdAndDisplayName(
|
|
1877
|
+
"environment provider",
|
|
1878
|
+
declaration
|
|
1879
|
+
);
|
|
1880
|
+
const description = z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS).optional().transform((value) => value ?? null).parse(declaration.description);
|
|
1881
|
+
const icon = z4.string().trim().min(1).optional().transform((value) => value ?? null).parse(declaration.icon);
|
|
1882
|
+
if (icon !== null) {
|
|
1883
|
+
assertProviderIconShape("environment provider", id, icon);
|
|
1884
|
+
}
|
|
1885
|
+
const requires = normalizeEnvironmentProviderRequirements(id, declaration);
|
|
1886
|
+
const inputs = normalizeProviderInputs(
|
|
1887
|
+
"environment provider",
|
|
1888
|
+
id,
|
|
1889
|
+
declaration.inputs
|
|
1890
|
+
);
|
|
1891
|
+
if (typeof declaration.create !== "function" || typeof declaration.remove !== "function") {
|
|
1892
|
+
throw new Error(
|
|
1893
|
+
`environment provider "${id}" must declare create and remove functions`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
assertOptionalFunction(
|
|
1897
|
+
"environment provider",
|
|
1898
|
+
id,
|
|
1899
|
+
declaration.validate,
|
|
1900
|
+
"a validate"
|
|
1901
|
+
);
|
|
1902
|
+
assertOptionalFunction(
|
|
1903
|
+
"environment provider",
|
|
1904
|
+
id,
|
|
1905
|
+
declaration.availability,
|
|
1906
|
+
"availability"
|
|
1907
|
+
);
|
|
1908
|
+
return {
|
|
1909
|
+
id,
|
|
1910
|
+
displayName,
|
|
1911
|
+
description,
|
|
1912
|
+
icon,
|
|
1913
|
+
requires,
|
|
1914
|
+
inputs: inputs === null ? null : inputs.schema,
|
|
1915
|
+
inputsJsonSchema: inputs === null ? null : inputs.jsonSchema,
|
|
1916
|
+
availability: declaration.availability ?? null,
|
|
1917
|
+
validate: declaration.validate ?? null,
|
|
1918
|
+
create: declaration.create,
|
|
1919
|
+
remove: declaration.remove,
|
|
1920
|
+
policy: environmentProviderPolicySchema.parse(declaration.policy ?? {})
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1870
1923
|
function normalizeEnvironmentProviderRequirements(id, declaration) {
|
|
1871
1924
|
const requires = declaration.requires ?? {};
|
|
1872
1925
|
if (typeof requires !== "object" || requires === null || Array.isArray(requires)) {
|
|
@@ -1898,23 +1951,747 @@ var environmentProviderPolicySchema = z4.object({
|
|
|
1898
1951
|
retireGraceMs: z4.number().int().nonnegative().nullable().default(3e5),
|
|
1899
1952
|
pathKeys: z4.enum(["per-thread", "per-attempt"]).default("per-thread")
|
|
1900
1953
|
}).strict();
|
|
1901
|
-
var
|
|
1954
|
+
var MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS = 200;
|
|
1955
|
+
function validatePluginMachineProviderDeclaration(declaration) {
|
|
1956
|
+
const { id, displayName } = requireProviderIdAndDisplayName(
|
|
1957
|
+
"machine provider",
|
|
1958
|
+
declaration
|
|
1959
|
+
);
|
|
1960
|
+
const description = z4.string().trim().min(1).max(MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS).parse(declaration.description);
|
|
1961
|
+
const icon = z4.string().trim().min(1).parse(declaration.icon);
|
|
1962
|
+
assertProviderIconShape("machine provider", id, icon);
|
|
1963
|
+
const ephemeral = z4.boolean().default(false).parse(declaration.ephemeral);
|
|
1964
|
+
const inputs = normalizeProviderInputs(
|
|
1965
|
+
"machine provider",
|
|
1966
|
+
id,
|
|
1967
|
+
declaration.inputs
|
|
1968
|
+
);
|
|
1969
|
+
if (typeof declaration.create !== "function" || typeof declaration.reconcileCleanup !== "function" || typeof declaration.remove !== "function") {
|
|
1970
|
+
throw new Error(
|
|
1971
|
+
`machine provider "${id}" must declare create, reconcileCleanup and remove functions`
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
const hasSuspend = typeof declaration.suspend === "function";
|
|
1975
|
+
const hasResume = typeof declaration.resume === "function";
|
|
1976
|
+
if (hasSuspend !== hasResume) {
|
|
1977
|
+
throw new Error(
|
|
1978
|
+
`machine provider "${id}" must declare suspend and resume together`
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
assertOptionalFunction(
|
|
1982
|
+
"machine provider",
|
|
1983
|
+
id,
|
|
1984
|
+
declaration.validate,
|
|
1985
|
+
"a validate"
|
|
1986
|
+
);
|
|
1987
|
+
assertOptionalFunction(
|
|
1988
|
+
"machine provider",
|
|
1989
|
+
id,
|
|
1990
|
+
declaration.availability,
|
|
1991
|
+
"availability"
|
|
1992
|
+
);
|
|
1993
|
+
return {
|
|
1994
|
+
id,
|
|
1995
|
+
displayName,
|
|
1996
|
+
description,
|
|
1997
|
+
icon,
|
|
1998
|
+
ephemeral,
|
|
1999
|
+
inputs: inputs === null ? null : inputs.schema,
|
|
2000
|
+
inputsJsonSchema: inputs === null ? null : inputs.jsonSchema,
|
|
2001
|
+
availability: declaration.availability ?? null,
|
|
2002
|
+
validate: declaration.validate ?? null,
|
|
2003
|
+
reconcileCleanup: declaration.reconcileCleanup,
|
|
2004
|
+
create: declaration.create,
|
|
2005
|
+
suspend: declaration.suspend ?? null,
|
|
2006
|
+
resume: declaration.resume ?? null,
|
|
2007
|
+
remove: declaration.remove
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
function validateServerAccessProviderDeclaration(declaration) {
|
|
2011
|
+
if (typeof declaration !== "object" || declaration === null)
|
|
2012
|
+
throw new Error("Invalid server access provider declaration");
|
|
2013
|
+
if (typeof declaration.id !== "string" || !/^[a-z][a-z0-9-]*$/u.test(declaration.id) || declaration.id === "direct")
|
|
2014
|
+
throw new Error("Invalid or reserved server access provider id");
|
|
2015
|
+
if (typeof declaration.displayName !== "string" || !declaration.displayName.trim() || typeof declaration.description !== "string" || !declaration.description.trim() || typeof declaration.availability !== "function" || typeof declaration.acquire !== "function" || typeof declaration.release !== "function")
|
|
2016
|
+
throw new Error("Invalid server access provider declaration");
|
|
2017
|
+
return declaration;
|
|
2018
|
+
}
|
|
2019
|
+
var LEGACY_UNKNOWN_MIGRATION_HASH = "legacy-unknown";
|
|
2020
|
+
function migrationStatementHash(statement) {
|
|
2021
|
+
return createHash("sha256").update(statement).digest("hex");
|
|
2022
|
+
}
|
|
2023
|
+
function runPluginStorageMigrations(database, statements) {
|
|
2024
|
+
database.exec(
|
|
2025
|
+
"CREATE TABLE IF NOT EXISTS _bb_migrations (id INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL, statement_hash TEXT)"
|
|
2026
|
+
);
|
|
2027
|
+
const migrationColumns = database.prepare("PRAGMA table_info(_bb_migrations)").all();
|
|
2028
|
+
if (!migrationColumns.some((column) => column.name === "statement_hash")) {
|
|
2029
|
+
database.exec("ALTER TABLE _bb_migrations ADD COLUMN statement_hash TEXT");
|
|
2030
|
+
}
|
|
2031
|
+
const rows = database.prepare(
|
|
2032
|
+
"SELECT id, statement_hash FROM _bb_migrations ORDER BY id"
|
|
2033
|
+
).all();
|
|
2034
|
+
const applied = /* @__PURE__ */ new Map();
|
|
2035
|
+
for (const row of rows) applied.set(row.id, row.statement_hash);
|
|
2036
|
+
const statementHashes = statements.map(migrationStatementHash);
|
|
2037
|
+
statementHashes.forEach((statementHash, index) => {
|
|
2038
|
+
const recordedHash = applied.get(index);
|
|
2039
|
+
if (recordedHash !== void 0 && recordedHash !== null && recordedHash !== statementHash) {
|
|
2040
|
+
throw new Error(
|
|
2041
|
+
`migration ${index} does not match the recorded statement; append a new migration instead of changing or reusing an index`
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
});
|
|
2045
|
+
const adopt = database.prepare(
|
|
2046
|
+
"UPDATE _bb_migrations SET statement_hash = ? WHERE id = ? AND statement_hash IS NULL"
|
|
2047
|
+
);
|
|
2048
|
+
const record = database.prepare(
|
|
2049
|
+
"INSERT INTO _bb_migrations (id, applied_at, statement_hash) VALUES (?, ?, ?)"
|
|
2050
|
+
);
|
|
2051
|
+
database.transaction(() => {
|
|
2052
|
+
for (const row of rows) {
|
|
2053
|
+
if (row.statement_hash !== null) continue;
|
|
2054
|
+
adopt.run(
|
|
2055
|
+
statementHashes[row.id] ?? LEGACY_UNKNOWN_MIGRATION_HASH,
|
|
2056
|
+
row.id
|
|
2057
|
+
);
|
|
2058
|
+
}
|
|
2059
|
+
statements.forEach((statement, index) => {
|
|
2060
|
+
if (applied.has(index)) return;
|
|
2061
|
+
database.exec(statement);
|
|
2062
|
+
record.run(index, Date.now(), statementHashes[index]);
|
|
2063
|
+
});
|
|
2064
|
+
})();
|
|
2065
|
+
}
|
|
2066
|
+
function normalizeHttpRouteRegistration(method, path, handler, opts, existingRoutes) {
|
|
2067
|
+
const normalizedMethod = String(method).toUpperCase();
|
|
2068
|
+
if (!PLUGIN_HTTP_METHODS.has(normalizedMethod)) {
|
|
2069
|
+
throw new Error(
|
|
2070
|
+
`invalid http method "${String(method)}" \u2014 use one of: ${[...PLUGIN_HTTP_METHODS].join(", ")}`
|
|
2071
|
+
);
|
|
2072
|
+
}
|
|
2073
|
+
if (typeof path !== "string" || !path.startsWith("/")) {
|
|
2074
|
+
throw new Error(
|
|
2075
|
+
`http route path must be a string starting with "/", got ${JSON.stringify(path)}`
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
if (typeof handler !== "function") {
|
|
2079
|
+
throw new Error(
|
|
2080
|
+
`http route handler for ${normalizedMethod} ${path} must be a function`
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
const auth = opts?.auth ?? "local";
|
|
2084
|
+
if (auth !== "local" && auth !== "token" && auth !== "none") {
|
|
2085
|
+
throw new Error(
|
|
2086
|
+
`invalid auth mode "${String(auth)}" for ${normalizedMethod} ${path} \u2014 use "local", "token", or "none"`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
if (existingRoutes.some(
|
|
2090
|
+
(route) => route.method === normalizedMethod && route.path === path
|
|
2091
|
+
)) {
|
|
2092
|
+
throw new Error(
|
|
2093
|
+
`http route ${normalizedMethod} ${path} is already registered`
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
return { method: normalizedMethod, path, auth };
|
|
2097
|
+
}
|
|
2098
|
+
function normalizeWebSocketRouteRegistration(path, handler, opts, existingRoutes) {
|
|
2099
|
+
if (typeof path !== "string" || !path.startsWith("/")) {
|
|
2100
|
+
throw new Error(
|
|
2101
|
+
`websocket route path must be a string starting with "/", got ${JSON.stringify(path)}`
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
if (typeof handler !== "function") {
|
|
2105
|
+
throw new Error(`websocket route handler for ${path} must be a function`);
|
|
2106
|
+
}
|
|
2107
|
+
const auth = opts?.auth ?? "local";
|
|
2108
|
+
if (auth !== "local" && auth !== "token" && auth !== "none") {
|
|
2109
|
+
throw new Error(
|
|
2110
|
+
`invalid auth mode "${String(auth)}" for websocket ${path} \u2014 use "local", "token", or "none"`
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2113
|
+
if (existingRoutes.some((route) => route.path === path)) {
|
|
2114
|
+
throw new Error(`websocket route ${path} is already registered`);
|
|
2115
|
+
}
|
|
2116
|
+
return { path, auth };
|
|
2117
|
+
}
|
|
2118
|
+
function normalizeRpcRegistration(contract, handlers, registered) {
|
|
2119
|
+
if (typeof contract !== "object" || contract === null || Array.isArray(contract)) {
|
|
2120
|
+
throw new Error("rpc.register contract must be an object");
|
|
2121
|
+
}
|
|
2122
|
+
if (typeof handlers !== "object" || handlers === null || Array.isArray(handlers)) {
|
|
2123
|
+
throw new Error("rpc.register handlers must be an object");
|
|
2124
|
+
}
|
|
2125
|
+
const pending = [];
|
|
2126
|
+
const contractEntries = Object.entries(contract);
|
|
2127
|
+
const contractNames = new Set(contractEntries.map(([name]) => name));
|
|
2128
|
+
for (const extraName of Object.keys(handlers)) {
|
|
2129
|
+
if (!contractNames.has(extraName)) {
|
|
2130
|
+
throw new Error(
|
|
2131
|
+
`rpc handler "${extraName}" has no matching contract method`
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
for (const [name, methodContractValue] of contractEntries) {
|
|
2136
|
+
if (!RPC_METHOD_PATTERN.test(name)) {
|
|
2137
|
+
throw new Error(
|
|
2138
|
+
`invalid rpc method name "${name}" \u2014 use dot-separated segments with letters, digits, "-" and "_"`
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
const methodContract = readRpcMethodContract(name, methodContractValue);
|
|
2142
|
+
const handler = Reflect.get(handlers, name);
|
|
2143
|
+
if (typeof handler !== "function") {
|
|
2144
|
+
throw new Error(`rpc method "${name}" must provide a handler function`);
|
|
2145
|
+
}
|
|
2146
|
+
if (registered.has(name)) {
|
|
2147
|
+
throw new Error(`rpc method "${name}" is already registered`);
|
|
2148
|
+
}
|
|
2149
|
+
pending.push([
|
|
2150
|
+
name,
|
|
2151
|
+
{
|
|
2152
|
+
inputSchema: methodContract.input,
|
|
2153
|
+
outputSchema: methodContract.output,
|
|
2154
|
+
handler
|
|
2155
|
+
}
|
|
2156
|
+
]);
|
|
2157
|
+
}
|
|
2158
|
+
return pending;
|
|
2159
|
+
}
|
|
2160
|
+
function normalizeRealtimePayload(channel, payload) {
|
|
2161
|
+
if (typeof channel !== "string" || channel.length === 0) {
|
|
2162
|
+
throw new Error("realtime channel must be a non-empty string");
|
|
2163
|
+
}
|
|
2164
|
+
if (payload === void 0) return null;
|
|
2165
|
+
let json;
|
|
2166
|
+
try {
|
|
2167
|
+
json = JSON.stringify(payload);
|
|
2168
|
+
} catch {
|
|
2169
|
+
json = void 0;
|
|
2170
|
+
}
|
|
2171
|
+
if (json === void 0) {
|
|
2172
|
+
throw new Error(
|
|
2173
|
+
`realtime payload for channel "${channel}" is not JSON-serializable`
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
return JSON.parse(json);
|
|
2177
|
+
}
|
|
2178
|
+
function validateBackgroundServiceRegistration(name, service, existing) {
|
|
2179
|
+
if (typeof name !== "string" || !BACKGROUND_NAME_PATTERN.test(name)) {
|
|
2180
|
+
throw new Error(
|
|
2181
|
+
`invalid service name ${JSON.stringify(name)} \u2014 use letters, digits, "-" and "_"`
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
if (existing.some((record) => record.name === name)) {
|
|
2185
|
+
throw new Error(`background service "${name}" is already registered`);
|
|
2186
|
+
}
|
|
2187
|
+
if (typeof service?.start !== "function") {
|
|
2188
|
+
throw new Error(
|
|
2189
|
+
`background service "${name}" must provide a start(signal) function`
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
return { name, start: service.start.bind(service) };
|
|
2193
|
+
}
|
|
2194
|
+
function validateScheduleRegistration(name, cron, fn, existing, parseCron) {
|
|
2195
|
+
if (typeof name !== "string" || !BACKGROUND_NAME_PATTERN.test(name)) {
|
|
2196
|
+
throw new Error(
|
|
2197
|
+
`invalid schedule name ${JSON.stringify(name)} \u2014 use letters, digits, "-" and "_"`
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
if (existing.some((record) => record.name === name)) {
|
|
2201
|
+
throw new Error(`schedule "${name}" is already registered`);
|
|
2202
|
+
}
|
|
2203
|
+
try {
|
|
2204
|
+
parseCron(String(cron));
|
|
2205
|
+
} catch (error) {
|
|
2206
|
+
throw new Error(
|
|
2207
|
+
`invalid cron ${JSON.stringify(cron)} for schedule "${name}": ${error instanceof Error ? error.message : String(error)}`
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
if (typeof fn !== "function") {
|
|
2211
|
+
throw new Error(`schedule "${name}" must provide a function`);
|
|
2212
|
+
}
|
|
2213
|
+
return { name, cron: String(cron), fn };
|
|
2214
|
+
}
|
|
2215
|
+
function normalizeCliRegistration(registration, alreadyRegistered) {
|
|
2216
|
+
if (alreadyRegistered) {
|
|
2217
|
+
throw new Error("cli command is already registered");
|
|
2218
|
+
}
|
|
2219
|
+
const name = registration?.name;
|
|
2220
|
+
if (typeof name !== "string" || !CLI_COMMAND_NAME_PATTERN.test(name)) {
|
|
2221
|
+
throw new Error(
|
|
2222
|
+
`invalid cli command name ${JSON.stringify(name)} \u2014 use lowercase letters, digits, and "-"`
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
if (typeof registration.summary !== "string" || registration.summary.trim().length === 0) {
|
|
2226
|
+
throw new Error(`cli command "${name}" must provide a summary`);
|
|
2227
|
+
}
|
|
2228
|
+
const commands = registration.commands ?? [];
|
|
2229
|
+
if (!Array.isArray(commands)) {
|
|
2230
|
+
throw new Error(`cli command "${name}" commands must be an array`);
|
|
2231
|
+
}
|
|
2232
|
+
const validatedCommands = commands.map((command, index) => {
|
|
2233
|
+
if (typeof command?.name !== "string" || !CLI_COMMAND_NAME_PATTERN.test(command.name) || typeof command.summary !== "string" || typeof command.usage !== "string") {
|
|
2234
|
+
throw new Error(
|
|
2235
|
+
`cli command "${name}" commands[${index}] must be { name: [a-z0-9-]+, summary, usage }`
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2238
|
+
return {
|
|
2239
|
+
name: command.name,
|
|
2240
|
+
summary: command.summary,
|
|
2241
|
+
usage: command.usage
|
|
2242
|
+
};
|
|
2243
|
+
});
|
|
2244
|
+
if (typeof registration.run !== "function") {
|
|
2245
|
+
throw new Error(
|
|
2246
|
+
`cli command "${name}" must provide a run(argv, ctx) function`
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
return {
|
|
2250
|
+
name,
|
|
2251
|
+
summary: registration.summary,
|
|
2252
|
+
commands: validatedCommands,
|
|
2253
|
+
run: registration.run.bind(registration)
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
function normalizeAgentToolRegistration(args) {
|
|
2257
|
+
const { pluginId, declaredIconNames, tool } = args;
|
|
2258
|
+
const name = tool?.name;
|
|
2259
|
+
if (typeof name !== "string" || !AGENT_TOOL_NAME_PATTERN.test(name)) {
|
|
2260
|
+
throw new Error(
|
|
2261
|
+
`invalid tool name ${JSON.stringify(name)} \u2014 use letters, digits, "-" and "_"`
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
if (RESERVED_AGENT_TOOL_NAMES.includes(name)) {
|
|
2265
|
+
throw new Error(
|
|
2266
|
+
`tool name "${name}" is a built-in bb tool \u2014 pick another name`
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
rejectStaleAgentToolFields(name, tool);
|
|
2270
|
+
if (typeof tool.description !== "string" || tool.description.trim().length === 0) {
|
|
2271
|
+
throw new Error(`tool "${name}" must provide a description`);
|
|
2272
|
+
}
|
|
2273
|
+
if (tool.instructions !== void 0 && typeof tool.instructions !== "string") {
|
|
2274
|
+
throw new Error(`tool "${name}" instructions must be a string`);
|
|
2275
|
+
}
|
|
2276
|
+
if (typeof tool.instructions === "string" && tool.instructions.length > PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS) {
|
|
2277
|
+
throw new Error(
|
|
2278
|
+
`tool "${name}" instructions exceed the ${PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS}-character limit`
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
const presentation = parsePluginAgentToolPresentation(
|
|
2282
|
+
name,
|
|
2283
|
+
tool.presentation
|
|
2284
|
+
);
|
|
2285
|
+
if (presentation?.icon !== void 0) {
|
|
2286
|
+
const problem = undeclaredIconProblem(
|
|
2287
|
+
pluginId,
|
|
2288
|
+
declaredIconNames,
|
|
2289
|
+
presentation.icon.glyph
|
|
2290
|
+
);
|
|
2291
|
+
if (problem !== null) {
|
|
2292
|
+
throw new Error(agentToolIconRefusalMessage(name, problem));
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
if (typeof tool.execute !== "function") {
|
|
2296
|
+
throw new Error(
|
|
2297
|
+
`tool "${name}" must provide an execute(params, ctx) function`
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
const parameters = tool.parameters;
|
|
2301
|
+
let inputSchema;
|
|
2302
|
+
let parse;
|
|
2303
|
+
if (isZodSchemaLike(parameters)) {
|
|
2304
|
+
try {
|
|
2305
|
+
inputSchema = zodSchemaToJsonSchema(parameters);
|
|
2306
|
+
} catch (error) {
|
|
2307
|
+
throw new Error(
|
|
2308
|
+
`tool "${name}" parameters look like a zod schema but could not be converted to JSON Schema (${error instanceof Error ? error.message : String(error)}) \u2014 use zod 4, or pass a plain JSON-schema object`
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
parse = (input) => {
|
|
2312
|
+
const result = parameters.safeParse(input);
|
|
2313
|
+
if (result.success) return { ok: true, value: result.data };
|
|
2314
|
+
return { ok: false, error: summarizeParseIssues(result.error) };
|
|
2315
|
+
};
|
|
2316
|
+
} else if (typeof parameters === "object" && parameters !== null && !Array.isArray(parameters)) {
|
|
2317
|
+
try {
|
|
2318
|
+
inputSchema = JSON.parse(JSON.stringify(parameters));
|
|
2319
|
+
} catch {
|
|
2320
|
+
throw new Error(
|
|
2321
|
+
`tool "${name}" parameters JSON schema is not JSON-serializable`
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
parse = (input) => ({ ok: true, value: input });
|
|
2325
|
+
} else {
|
|
2326
|
+
throw new Error(
|
|
2327
|
+
`tool "${name}" parameters must be a zod schema or a JSON-schema object`
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
assertNoRecursiveJsonSchemaReferences(
|
|
2331
|
+
inputSchema,
|
|
2332
|
+
`tool "${name}" parameters`
|
|
2333
|
+
);
|
|
2334
|
+
return {
|
|
2335
|
+
name,
|
|
2336
|
+
description: tool.description,
|
|
2337
|
+
presentation,
|
|
2338
|
+
instructions: tool.instructions !== void 0 && tool.instructions.trim().length > 0 ? tool.instructions : null,
|
|
2339
|
+
inputSchema,
|
|
2340
|
+
parse,
|
|
2341
|
+
execute: tool.execute.bind(tool)
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
function normalizeMentionProviderRegistration(provider, existing) {
|
|
2345
|
+
const id = provider?.id;
|
|
2346
|
+
if (typeof id !== "string" || !MENTION_PROVIDER_ID_PATTERN.test(id)) {
|
|
2347
|
+
throw new Error(
|
|
2348
|
+
`invalid mention provider id ${JSON.stringify(id)} \u2014 use letters, digits, "-" and "_"`
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
if (existing.some((record) => record.id === id)) {
|
|
2352
|
+
throw new Error(`mention provider "${id}" is already registered`);
|
|
2353
|
+
}
|
|
2354
|
+
if (typeof provider.label !== "string" || provider.label.trim().length === 0) {
|
|
2355
|
+
throw new Error(`mention provider "${id}" must provide a label`);
|
|
2356
|
+
}
|
|
2357
|
+
if (typeof provider.search !== "function") {
|
|
2358
|
+
throw new Error(
|
|
2359
|
+
`mention provider "${id}" must provide a search({ query, projectId, threadId }) function`
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
if (typeof provider.resolve !== "function") {
|
|
2363
|
+
throw new Error(
|
|
2364
|
+
`mention provider "${id}" must provide a resolve(itemId) function`
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
return {
|
|
2368
|
+
id,
|
|
2369
|
+
label: provider.label.trim(),
|
|
2370
|
+
triggers: normalizeMentionProviderTriggers(id, provider.triggers),
|
|
2371
|
+
search: provider.search.bind(provider),
|
|
2372
|
+
resolve: provider.resolve.bind(provider)
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
function normalizeInteractionRequest(request) {
|
|
2376
|
+
if (!request || typeof request !== "object") {
|
|
2377
|
+
throw new Error("ui.requestInput requires an options object");
|
|
2378
|
+
}
|
|
2379
|
+
if (typeof request.threadId !== "string" || request.threadId.length === 0) {
|
|
2380
|
+
throw new Error("ui.requestInput threadId must be a non-empty string");
|
|
2381
|
+
}
|
|
2382
|
+
if (typeof request.rendererId !== "string" || !/^[a-zA-Z0-9_-]+$/.test(request.rendererId)) {
|
|
2383
|
+
throw new Error(
|
|
2384
|
+
"ui.requestInput rendererId must use letters, digits, '-' or '_'"
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
if (typeof request.title !== "string" || request.title.trim().length === 0 || request.title.trim().length > PLUGIN_INTERACTION_MAX_TITLE_LENGTH) {
|
|
2388
|
+
throw new Error(
|
|
2389
|
+
`ui.requestInput title must be 1-${PLUGIN_INTERACTION_MAX_TITLE_LENGTH} characters`
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
let payload;
|
|
2393
|
+
try {
|
|
2394
|
+
const json = JSON.stringify(request.payload);
|
|
2395
|
+
if (json === void 0) throw new Error();
|
|
2396
|
+
if (Buffer.byteLength(json, "utf8") > PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES) {
|
|
2397
|
+
throw new Error("ui.requestInput payload exceeds 64 KiB");
|
|
2398
|
+
}
|
|
2399
|
+
payload = JSON.parse(json);
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
if (error instanceof Error && error.message.includes("64 KiB")) {
|
|
2402
|
+
throw error;
|
|
2403
|
+
}
|
|
2404
|
+
throw new Error("ui.requestInput payload must be JSON-serializable");
|
|
2405
|
+
}
|
|
2406
|
+
const timeoutMs = request.timeoutMs ?? 10 * 60 * 1e3;
|
|
2407
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 60 * 60 * 1e3) {
|
|
2408
|
+
throw new Error("ui.requestInput timeoutMs must be between 1 and 3600000");
|
|
2409
|
+
}
|
|
2410
|
+
return {
|
|
2411
|
+
threadId: request.threadId,
|
|
2412
|
+
rendererId: request.rendererId,
|
|
2413
|
+
title: request.title.trim(),
|
|
2414
|
+
payload,
|
|
2415
|
+
timeoutMs
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
function validateProviderEnvContribution(label, providerId, resolve, registered) {
|
|
2419
|
+
if (typeof providerId !== "string" || providerId.trim().length === 0) {
|
|
2420
|
+
throw new Error(`${label} requires a provider id`);
|
|
2421
|
+
}
|
|
2422
|
+
if (registered.has(providerId)) {
|
|
2423
|
+
throw new Error(`${label} for "${providerId}" is already registered`);
|
|
2424
|
+
}
|
|
2425
|
+
if (typeof resolve !== "function") {
|
|
2426
|
+
throw new Error(`${label} requires a resolver function`);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
function normalizePluginAgentToolParameters(args) {
|
|
2430
|
+
const { index, value } = args;
|
|
2431
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2432
|
+
throw new Error(
|
|
2433
|
+
`configure() output.tools[${index}].parameters must be a JSON-schema object`
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
let serialized;
|
|
2437
|
+
try {
|
|
2438
|
+
serialized = JSON.stringify(value);
|
|
2439
|
+
} catch {
|
|
2440
|
+
throw new Error(
|
|
2441
|
+
`configure() output.tools[${index}].parameters is not JSON-serializable`
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2444
|
+
if (serialized === void 0) {
|
|
2445
|
+
throw new Error(
|
|
2446
|
+
`configure() output.tools[${index}].parameters is not JSON-serializable`
|
|
2447
|
+
);
|
|
2448
|
+
}
|
|
2449
|
+
if (Buffer.byteLength(serialized, "utf8") > PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES) {
|
|
2450
|
+
throw new Error(
|
|
2451
|
+
`configure() output.tools[${index}].parameters exceeds the ${PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES}-byte limit`
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2454
|
+
const parameters = JSON.parse(serialized);
|
|
2455
|
+
if (parameters.type !== "object") {
|
|
2456
|
+
throw new Error(
|
|
2457
|
+
`configure() output.tools[${index}].parameters must have root type "object"`
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
assertNoRecursiveJsonSchemaReferences(
|
|
2461
|
+
parameters,
|
|
2462
|
+
`configure() output.tools[${index}].parameters`
|
|
2463
|
+
);
|
|
2464
|
+
return parameters;
|
|
2465
|
+
}
|
|
2466
|
+
function normalizePluginAgentToolSelections(args) {
|
|
2467
|
+
if (!Array.isArray(args.value)) {
|
|
2468
|
+
throw new Error("configure() output.tools must be an array");
|
|
2469
|
+
}
|
|
2470
|
+
if (args.value.length > PLUGIN_AGENT_SELECTION_MAX_IDS) {
|
|
2471
|
+
throw new Error(
|
|
2472
|
+
`configure() output.tools exceeds the ${PLUGIN_AGENT_SELECTION_MAX_IDS}-id limit`
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
const toolIds = [];
|
|
2476
|
+
const parameterOverrides = /* @__PURE__ */ new Map();
|
|
2477
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2478
|
+
for (let index = 0; index < args.value.length; index += 1) {
|
|
2479
|
+
const entry = args.value[index];
|
|
2480
|
+
let name;
|
|
2481
|
+
let parameters = null;
|
|
2482
|
+
if (typeof entry === "string") {
|
|
2483
|
+
name = entry;
|
|
2484
|
+
} else if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
|
|
2485
|
+
const typed = entry;
|
|
2486
|
+
const unknownKeys = Object.keys(typed).filter((key) => !["name", "parameters"].includes(key)).sort();
|
|
2487
|
+
if (unknownKeys.length > 0) {
|
|
2488
|
+
throw new Error(
|
|
2489
|
+
`configure() output.tools[${index}] contains unknown field${unknownKeys.length === 1 ? "" : "s"}: ${unknownKeys.join(", ")}`
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
name = typed.name;
|
|
2493
|
+
parameters = normalizePluginAgentToolParameters({
|
|
2494
|
+
index,
|
|
2495
|
+
value: typed.parameters
|
|
2496
|
+
});
|
|
2497
|
+
} else {
|
|
2498
|
+
throw new Error(
|
|
2499
|
+
`configure() output.tools[${index}] must be a tool name or { name, parameters }`
|
|
2500
|
+
);
|
|
2501
|
+
}
|
|
2502
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
2503
|
+
throw new Error(
|
|
2504
|
+
`configure() output.tools[${index}] must ${typeof entry === "string" ? "be" : "name"} a non-empty string`
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
if (seen.has(name)) {
|
|
2508
|
+
throw new Error(
|
|
2509
|
+
`configure() output.tools contains duplicate id ${JSON.stringify(name)}`
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
if (!args.knownIds.has(name)) {
|
|
2513
|
+
throw new Error(
|
|
2514
|
+
`configure() selected unknown tool id ${JSON.stringify(name)} owned by plugin ${JSON.stringify(args.pluginId)}`
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
seen.add(name);
|
|
2518
|
+
toolIds.push(name);
|
|
2519
|
+
if (parameters !== null) parameterOverrides.set(name, parameters);
|
|
2520
|
+
}
|
|
2521
|
+
return { toolIds, parameterOverrides };
|
|
2522
|
+
}
|
|
2523
|
+
function normalizePluginAgentSelectionIds(args) {
|
|
2524
|
+
if (!Array.isArray(args.value)) {
|
|
2525
|
+
throw new Error("configure() output.skills must be an array");
|
|
2526
|
+
}
|
|
2527
|
+
if (args.value.length > PLUGIN_AGENT_SELECTION_MAX_IDS) {
|
|
2528
|
+
throw new Error(
|
|
2529
|
+
`configure() output.skills exceeds the ${PLUGIN_AGENT_SELECTION_MAX_IDS}-id limit`
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2532
|
+
const selected = [];
|
|
2533
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2534
|
+
for (let index = 0; index < args.value.length; index += 1) {
|
|
2535
|
+
const id = args.value[index];
|
|
2536
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
2537
|
+
throw new Error(
|
|
2538
|
+
`configure() output.skills[${index}] must be a non-empty string`
|
|
2539
|
+
);
|
|
2540
|
+
}
|
|
2541
|
+
if (seen.has(id)) {
|
|
2542
|
+
throw new Error(
|
|
2543
|
+
`configure() output.skills contains duplicate id ${JSON.stringify(id)}`
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
if (!args.knownIds.has(id)) {
|
|
2547
|
+
throw new Error(
|
|
2548
|
+
`configure() selected unknown skill id ${JSON.stringify(id)} owned by plugin ${JSON.stringify(args.pluginId)}`
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
seen.add(id);
|
|
2552
|
+
selected.push(id);
|
|
2553
|
+
}
|
|
2554
|
+
return selected;
|
|
2555
|
+
}
|
|
2556
|
+
function normalizePluginAgentConfiguration(args) {
|
|
2557
|
+
if (typeof args.value !== "object" || args.value === null || Array.isArray(args.value)) {
|
|
2558
|
+
throw new Error(
|
|
2559
|
+
"configure() must return { tools: string[], skills: string[], instructions?: string }"
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
const output = args.value;
|
|
2563
|
+
const unknownKeys = Object.keys(output).filter((key) => !["tools", "skills", "instructions"].includes(key)).sort();
|
|
2564
|
+
if (unknownKeys.length > 0) {
|
|
2565
|
+
throw new Error(
|
|
2566
|
+
`configure() output contains unknown field${unknownKeys.length === 1 ? "" : "s"}: ${unknownKeys.join(", ")}`
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
if (output.instructions !== void 0 && typeof output.instructions !== "string") {
|
|
2570
|
+
throw new Error("configure() output.instructions must be a string");
|
|
2571
|
+
}
|
|
2572
|
+
const instructions = typeof output.instructions === "string" && output.instructions.trim().length > 0 ? output.instructions.slice(
|
|
2573
|
+
0,
|
|
2574
|
+
PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS
|
|
2575
|
+
) : null;
|
|
2576
|
+
const toolSelections = normalizePluginAgentToolSelections({
|
|
2577
|
+
knownIds: args.knownToolIds,
|
|
2578
|
+
pluginId: args.pluginId,
|
|
2579
|
+
value: output.tools
|
|
2580
|
+
});
|
|
2581
|
+
return {
|
|
2582
|
+
toolIds: toolSelections.toolIds,
|
|
2583
|
+
toolParameterOverrides: toolSelections.parameterOverrides,
|
|
2584
|
+
skillIds: normalizePluginAgentSelectionIds({
|
|
2585
|
+
knownIds: args.knownSkillIds,
|
|
2586
|
+
pluginId: args.pluginId,
|
|
2587
|
+
value: output.skills
|
|
2588
|
+
}),
|
|
2589
|
+
instructions
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
function normalizeRpcIssuePath(path) {
|
|
2593
|
+
if (path === void 0) return void 0;
|
|
2594
|
+
const segments = Array.isArray(path) ? path : [path];
|
|
2595
|
+
const normalized = segments.map((segment) => {
|
|
2596
|
+
const key = typeof segment === "object" && segment !== null ? Reflect.get(segment, "key") : segment;
|
|
2597
|
+
return typeof key === "number" ? key : String(key);
|
|
2598
|
+
});
|
|
2599
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
2600
|
+
}
|
|
2601
|
+
function normalizeRpcIssues(issues) {
|
|
2602
|
+
return issues.map((issue) => {
|
|
2603
|
+
const path = normalizeRpcIssuePath(issue.path);
|
|
2604
|
+
return {
|
|
2605
|
+
message: issue.message,
|
|
2606
|
+
...path !== void 0 ? { path } : {}
|
|
2607
|
+
};
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
async function validateRpcValue(schema, value, phase, fail) {
|
|
2611
|
+
let result;
|
|
2612
|
+
try {
|
|
2613
|
+
result = await schema["~standard"].validate(value);
|
|
2614
|
+
} catch (error) {
|
|
2615
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2616
|
+
return fail({
|
|
2617
|
+
code: phase === "input" ? "invalid_input" : "invalid_output",
|
|
2618
|
+
message: `rpc ${phase} validator failed: ${detail}`,
|
|
2619
|
+
issues: [{ message: detail }]
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
if (result.issues !== void 0) {
|
|
2623
|
+
return fail({
|
|
2624
|
+
code: phase === "input" ? "invalid_input" : "invalid_output",
|
|
2625
|
+
message: `rpc ${phase} validation failed`,
|
|
2626
|
+
issues: normalizeRpcIssues(result.issues)
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
return result.value;
|
|
2630
|
+
}
|
|
2631
|
+
function normalizeRpcJsonResult(value, fail) {
|
|
2632
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
2633
|
+
function visit(current, path) {
|
|
2634
|
+
if (current === null || typeof current === "string" || typeof current === "boolean") {
|
|
2635
|
+
return current;
|
|
2636
|
+
}
|
|
2637
|
+
if (typeof current === "number") {
|
|
2638
|
+
if (!Number.isFinite(current)) {
|
|
2639
|
+
return fail({
|
|
2640
|
+
code: "non_json_result",
|
|
2641
|
+
message: `rpc result at ${path} contains a non-finite number`
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
return current;
|
|
2645
|
+
}
|
|
2646
|
+
if (typeof current !== "object") {
|
|
2647
|
+
return fail({
|
|
2648
|
+
code: "non_json_result",
|
|
2649
|
+
message: `rpc result at ${path} is not a JSON value (${typeof current})`
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
if (ancestors.has(current)) {
|
|
2653
|
+
return fail({
|
|
2654
|
+
code: "non_json_result",
|
|
2655
|
+
message: `rpc result at ${path} is cyclic`
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
ancestors.add(current);
|
|
2659
|
+
try {
|
|
2660
|
+
if (Array.isArray(current)) {
|
|
2661
|
+
return current.map((item, index) => visit(item, `${path}[${index}]`));
|
|
2662
|
+
}
|
|
2663
|
+
const prototype = Object.getPrototypeOf(current);
|
|
2664
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
2665
|
+
return fail({
|
|
2666
|
+
code: "non_json_result",
|
|
2667
|
+
message: `rpc result at ${path} must be a plain JSON object`
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
if (Reflect.ownKeys(current).some((key) => typeof key === "symbol")) {
|
|
2671
|
+
return fail({
|
|
2672
|
+
code: "non_json_result",
|
|
2673
|
+
message: `rpc result at ${path} contains a symbol key`
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
const normalized = {};
|
|
2677
|
+
for (const [key, child] of Object.entries(current)) {
|
|
2678
|
+
normalized[key] = visit(child, `${path}.${key}`);
|
|
2679
|
+
}
|
|
2680
|
+
return normalized;
|
|
2681
|
+
} finally {
|
|
2682
|
+
ancestors.delete(current);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
return visit(value, "$result");
|
|
2686
|
+
}
|
|
1902
2687
|
export {
|
|
1903
|
-
|
|
1904
|
-
BACKGROUND_NAME_PATTERN,
|
|
1905
|
-
CLI_COMMAND_NAME_PATTERN,
|
|
2688
|
+
ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS,
|
|
1906
2689
|
ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS,
|
|
1907
2690
|
ENVIRONMENT_PROVIDER_ID_PATTERN,
|
|
1908
2691
|
ENVIRONMENT_PROVIDER_REQUIREMENT_NAMES,
|
|
1909
2692
|
KV_VALUE_MAX_BYTES,
|
|
1910
|
-
|
|
1911
|
-
MENTION_PROVIDER_ID_PATTERN,
|
|
1912
|
-
PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS,
|
|
1913
|
-
PLUGIN_AGENT_SELECTION_MAX_IDS,
|
|
1914
|
-
PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS,
|
|
2693
|
+
MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS,
|
|
1915
2694
|
PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS,
|
|
1916
|
-
PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES,
|
|
1917
|
-
PLUGIN_HTTP_METHODS,
|
|
1918
2695
|
PLUGIN_MENTION_TRIGGER_VALUES,
|
|
1919
2696
|
PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES,
|
|
1920
2697
|
PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES,
|
|
@@ -1926,37 +2703,47 @@ export {
|
|
|
1926
2703
|
PROVIDER_ID_PATTERN,
|
|
1927
2704
|
RESERVED_AGENT_TOOL_NAMES,
|
|
1928
2705
|
RESERVED_BB_CLI_COMMANDS,
|
|
1929
|
-
RPC_METHOD_PATTERN,
|
|
1930
2706
|
SERVER_DIRECT_AI_SERVICE_IDS,
|
|
1931
2707
|
SETTING_KEY_PATTERN,
|
|
1932
2708
|
adoptHttpRouteResponse,
|
|
1933
|
-
agentToolIconRefusalMessage,
|
|
1934
2709
|
aiServiceAlreadyRegisteredMessage,
|
|
1935
2710
|
assertAiServiceRegistrable,
|
|
1936
|
-
|
|
2711
|
+
coerceStoredPluginSettingValue,
|
|
1937
2712
|
deriveValidatedProviderOptions,
|
|
1938
2713
|
enforcePluginCliOutputLimit,
|
|
2714
|
+
environmentCompositionSchema,
|
|
1939
2715
|
isPluginMentionTrigger,
|
|
1940
2716
|
isStandardSchema,
|
|
1941
|
-
|
|
1942
|
-
|
|
2717
|
+
normalizeAgentToolRegistration,
|
|
2718
|
+
normalizeCliRegistration,
|
|
2719
|
+
normalizeHttpRouteRegistration,
|
|
2720
|
+
normalizeInteractionRequest,
|
|
2721
|
+
normalizeMentionProviderRegistration,
|
|
2722
|
+
normalizePluginAgentConfiguration,
|
|
2723
|
+
normalizeRealtimePayload,
|
|
2724
|
+
normalizeRpcJsonResult,
|
|
2725
|
+
normalizeRpcRegistration,
|
|
2726
|
+
normalizeWebSocketRouteRegistration,
|
|
1943
2727
|
parsePluginAgentToolPresentation,
|
|
1944
2728
|
pluginCliCollisionWarning,
|
|
1945
2729
|
pluginHookAlreadyRegisteredMessage,
|
|
1946
2730
|
providerAlreadyRegisteredMessage,
|
|
1947
2731
|
providerIconRefusalMessage,
|
|
1948
2732
|
providerWithoutBridgeMessage,
|
|
1949
|
-
readRpcMethodContract,
|
|
1950
2733
|
registerSettingDescriptors,
|
|
1951
|
-
|
|
2734
|
+
runPluginStorageMigrations,
|
|
1952
2735
|
storePluginHook,
|
|
1953
|
-
summarizeParseIssues,
|
|
1954
2736
|
summarizeStandardIssues,
|
|
1955
2737
|
undeclaredIconProblem,
|
|
2738
|
+
validateBackgroundServiceRegistration,
|
|
1956
2739
|
validatePluginAiServiceDeclaration,
|
|
1957
2740
|
validatePluginEnvironmentProviderDeclaration,
|
|
2741
|
+
validatePluginMachineProviderDeclaration,
|
|
1958
2742
|
validatePluginProviderDeclaration,
|
|
1959
2743
|
validatePluginProviderEnvEntries,
|
|
1960
|
-
|
|
1961
|
-
|
|
2744
|
+
validateProviderEnvContribution,
|
|
2745
|
+
validateRpcValue,
|
|
2746
|
+
validateScheduleRegistration,
|
|
2747
|
+
validateServerAccessProviderDeclaration,
|
|
2748
|
+
validateSettingsUpdate
|
|
1962
2749
|
};
|