@get-bb/plugin-sdk 0.4.84 → 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/bundled-types/bb-plugin-sdk-app.d.ts +9 -1
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +229 -43
- package/bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts +52 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge.d.ts +131 -2
- package/bundled-types/bb-plugin-sdk-testing.d.ts +17 -8
- package/bundled-types/bb-plugin-sdk.d.ts +171 -5
- package/dist/host.js +1 -1
- package/dist/internal/host-policy.js +790 -114
- package/dist/internal/plugin-app-collector.js +71 -90
- package/dist/provider-bridge-acp.js +1964 -2046
- package/dist/provider-bridge-testing.js +1378 -1405
- package/dist/provider-bridge-worker-entry.mjs +0 -1
- package/dist/provider-bridge.js +1455 -1499
- package/dist/testing/app.js +71 -90
- package/dist/testing/index.js +954 -791
- package/package.json +1 -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"];
|
|
@@ -514,6 +519,22 @@ function validateSettingsUpdate(descriptors, values) {
|
|
|
514
519
|
}
|
|
515
520
|
return errors;
|
|
516
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
|
+
}
|
|
517
538
|
var PLUGIN_MENTION_TRIGGER_VALUES = [
|
|
518
539
|
"@",
|
|
519
540
|
"#",
|
|
@@ -1792,71 +1813,47 @@ var environmentCompositionSchema = z4.object({
|
|
|
1792
1813
|
machineProviderId: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN),
|
|
1793
1814
|
environmentProviderId: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN)
|
|
1794
1815
|
}).strict();
|
|
1795
|
-
function
|
|
1816
|
+
function requireProviderIdAndDisplayName(noun, declaration) {
|
|
1796
1817
|
if (typeof declaration !== "object" || declaration === null) {
|
|
1797
|
-
throw new Error(
|
|
1818
|
+
throw new Error(`${noun} declaration must be an object`);
|
|
1798
1819
|
}
|
|
1799
1820
|
const id = declaration.id;
|
|
1800
1821
|
if (typeof id !== "string" || !ENVIRONMENT_PROVIDER_ID_PATTERN.test(id)) {
|
|
1801
1822
|
throw new Error(
|
|
1802
|
-
`invalid
|
|
1823
|
+
`invalid ${noun} id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, or "-", starting with a letter or digit`
|
|
1803
1824
|
);
|
|
1804
1825
|
}
|
|
1805
1826
|
const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
|
|
1806
1827
|
if (displayName.length === 0 || displayName.length > ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS) {
|
|
1807
1828
|
throw new Error(
|
|
1808
|
-
|
|
1829
|
+
`${noun} "${id}" needs a displayName of 1-${ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS} characters`
|
|
1809
1830
|
);
|
|
1810
1831
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1832
|
+
return { id, displayName };
|
|
1833
|
+
}
|
|
1834
|
+
function assertProviderIconShape(noun, id, icon) {
|
|
1835
|
+
if (isPluginOwnedIconPath(icon)) {
|
|
1814
1836
|
validateProviderRelativePath(icon, `"${id}" icon`);
|
|
1815
|
-
} else if (
|
|
1816
|
-
throw new Error(
|
|
1817
|
-
`environment provider "${id}" icon must be a glyph, declared icon, or plugin-relative path`
|
|
1818
|
-
);
|
|
1819
|
-
}
|
|
1820
|
-
const requires = normalizeEnvironmentProviderRequirements(id, declaration);
|
|
1821
|
-
const inputs = normalizeEnvironmentProviderInputs(id, declaration);
|
|
1822
|
-
if (typeof declaration.create !== "function" || typeof declaration.remove !== "function") {
|
|
1823
|
-
throw new Error(
|
|
1824
|
-
`environment provider "${id}" must declare create and remove functions`
|
|
1825
|
-
);
|
|
1826
|
-
}
|
|
1827
|
-
if (declaration.validate !== void 0 && typeof declaration.validate !== "function") {
|
|
1837
|
+
} else if (!isNamespacedGlyph(icon) && /[/\\]/u.test(icon)) {
|
|
1828
1838
|
throw new Error(
|
|
1829
|
-
|
|
1839
|
+
`${noun} "${id}" icon must be a glyph, declared icon, or plugin-relative path`
|
|
1830
1840
|
);
|
|
1831
1841
|
}
|
|
1832
|
-
|
|
1842
|
+
}
|
|
1843
|
+
function assertOptionalFunction(noun, id, value, phrase) {
|
|
1844
|
+
if (value !== void 0 && typeof value !== "function") {
|
|
1833
1845
|
throw new Error(
|
|
1834
|
-
|
|
1846
|
+
`${noun} "${id}" declares ${phrase} that is not a function`
|
|
1835
1847
|
);
|
|
1836
1848
|
}
|
|
1837
|
-
return {
|
|
1838
|
-
id,
|
|
1839
|
-
displayName,
|
|
1840
|
-
description,
|
|
1841
|
-
icon,
|
|
1842
|
-
requires,
|
|
1843
|
-
inputs: inputs === null ? null : inputs.schema,
|
|
1844
|
-
inputsJsonSchema: inputs === null ? null : inputs.jsonSchema,
|
|
1845
|
-
availability: declaration.availability ?? null,
|
|
1846
|
-
validate: declaration.validate ?? null,
|
|
1847
|
-
create: declaration.create,
|
|
1848
|
-
remove: declaration.remove,
|
|
1849
|
-
policy: environmentProviderPolicySchema.parse(declaration.policy ?? {})
|
|
1850
|
-
};
|
|
1851
1849
|
}
|
|
1852
|
-
function
|
|
1853
|
-
const inputs = declaration.inputs;
|
|
1850
|
+
function normalizeProviderInputs(noun, id, inputs) {
|
|
1854
1851
|
if (inputs === void 0) {
|
|
1855
1852
|
return null;
|
|
1856
1853
|
}
|
|
1857
1854
|
if (!isStandardSchema(inputs)) {
|
|
1858
1855
|
throw new Error(
|
|
1859
|
-
|
|
1856
|
+
`${noun} "${id}" declares an inputs that is not a Standard Schema v1 validator`
|
|
1860
1857
|
);
|
|
1861
1858
|
}
|
|
1862
1859
|
let converted;
|
|
@@ -1864,17 +1861,65 @@ function normalizeEnvironmentProviderInputs(id, declaration) {
|
|
|
1864
1861
|
converted = JSON.parse(JSON.stringify(standardSchemaToJsonSchema(inputs)));
|
|
1865
1862
|
} catch (error) {
|
|
1866
1863
|
throw new Error(
|
|
1867
|
-
|
|
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()`
|
|
1868
1865
|
);
|
|
1869
1866
|
}
|
|
1870
1867
|
const jsonSchema = jsonValueSchema.safeParse(converted);
|
|
1871
1868
|
if (!jsonSchema.success) {
|
|
1872
1869
|
throw new Error(
|
|
1873
|
-
|
|
1870
|
+
`${noun} "${id}" declares an inputs schema whose JSON Schema is not JSON-serializable`
|
|
1874
1871
|
);
|
|
1875
1872
|
}
|
|
1876
1873
|
return { schema: inputs, jsonSchema: jsonSchema.data };
|
|
1877
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
|
+
}
|
|
1878
1923
|
function normalizeEnvironmentProviderRequirements(id, declaration) {
|
|
1879
1924
|
const requires = declaration.requires ?? {};
|
|
1880
1925
|
if (typeof requires !== "object" || requires === null || Array.isArray(requires)) {
|
|
@@ -1908,32 +1953,19 @@ var environmentProviderPolicySchema = z4.object({
|
|
|
1908
1953
|
}).strict();
|
|
1909
1954
|
var MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS = 200;
|
|
1910
1955
|
function validatePluginMachineProviderDeclaration(declaration) {
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
if (typeof id !== "string" || !ENVIRONMENT_PROVIDER_ID_PATTERN.test(id)) {
|
|
1916
|
-
throw new Error(
|
|
1917
|
-
`invalid machine provider id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, or "-", starting with a letter or digit`
|
|
1918
|
-
);
|
|
1919
|
-
}
|
|
1920
|
-
const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
|
|
1921
|
-
if (displayName.length === 0 || displayName.length > ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS) {
|
|
1922
|
-
throw new Error(
|
|
1923
|
-
`machine provider "${id}" needs a displayName of 1-${ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS} characters`
|
|
1924
|
-
);
|
|
1925
|
-
}
|
|
1956
|
+
const { id, displayName } = requireProviderIdAndDisplayName(
|
|
1957
|
+
"machine provider",
|
|
1958
|
+
declaration
|
|
1959
|
+
);
|
|
1926
1960
|
const description = z4.string().trim().min(1).max(MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS).parse(declaration.description);
|
|
1927
1961
|
const icon = z4.string().trim().min(1).parse(declaration.icon);
|
|
1928
|
-
|
|
1929
|
-
validateProviderRelativePath(icon, `"${id}" icon`);
|
|
1930
|
-
} else if (!isNamespacedGlyph(icon) && /[/\\]/u.test(icon)) {
|
|
1931
|
-
throw new Error(
|
|
1932
|
-
`machine provider "${id}" icon must be a glyph, declared icon, or plugin-relative path`
|
|
1933
|
-
);
|
|
1934
|
-
}
|
|
1962
|
+
assertProviderIconShape("machine provider", id, icon);
|
|
1935
1963
|
const ephemeral = z4.boolean().default(false).parse(declaration.ephemeral);
|
|
1936
|
-
const inputs =
|
|
1964
|
+
const inputs = normalizeProviderInputs(
|
|
1965
|
+
"machine provider",
|
|
1966
|
+
id,
|
|
1967
|
+
declaration.inputs
|
|
1968
|
+
);
|
|
1937
1969
|
if (typeof declaration.create !== "function" || typeof declaration.reconcileCleanup !== "function" || typeof declaration.remove !== "function") {
|
|
1938
1970
|
throw new Error(
|
|
1939
1971
|
`machine provider "${id}" must declare create, reconcileCleanup and remove functions`
|
|
@@ -1946,16 +1978,18 @@ function validatePluginMachineProviderDeclaration(declaration) {
|
|
|
1946
1978
|
`machine provider "${id}" must declare suspend and resume together`
|
|
1947
1979
|
);
|
|
1948
1980
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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
|
+
);
|
|
1959
1993
|
return {
|
|
1960
1994
|
id,
|
|
1961
1995
|
displayName,
|
|
@@ -1973,56 +2007,691 @@ function validatePluginMachineProviderDeclaration(declaration) {
|
|
|
1973
2007
|
remove: declaration.remove
|
|
1974
2008
|
};
|
|
1975
2009
|
}
|
|
1976
|
-
function
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
if (
|
|
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)) {
|
|
1980
2069
|
throw new Error(
|
|
1981
|
-
`
|
|
2070
|
+
`invalid http method "${String(method)}" \u2014 use one of: ${[...PLUGIN_HTTP_METHODS].join(", ")}`
|
|
1982
2071
|
);
|
|
1983
2072
|
}
|
|
1984
|
-
|
|
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;
|
|
1985
2166
|
try {
|
|
1986
|
-
|
|
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));
|
|
1987
2205
|
} catch (error) {
|
|
1988
2206
|
throw new Error(
|
|
1989
|
-
`
|
|
2207
|
+
`invalid cron ${JSON.stringify(cron)} for schedule "${name}": ${error instanceof Error ? error.message : String(error)}`
|
|
1990
2208
|
);
|
|
1991
2209
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
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)) {
|
|
1994
2221
|
throw new Error(
|
|
1995
|
-
`
|
|
2222
|
+
`invalid cli command name ${JSON.stringify(name)} \u2014 use lowercase letters, digits, and "-"`
|
|
1996
2223
|
);
|
|
1997
2224
|
}
|
|
1998
|
-
|
|
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
|
+
};
|
|
1999
2255
|
}
|
|
2000
|
-
function
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
if (typeof
|
|
2004
|
-
throw new Error(
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
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");
|
|
2008
2686
|
}
|
|
2009
2687
|
export {
|
|
2010
|
-
AGENT_TOOL_NAME_PATTERN,
|
|
2011
|
-
BACKGROUND_NAME_PATTERN,
|
|
2012
|
-
CLI_COMMAND_NAME_PATTERN,
|
|
2013
2688
|
ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS,
|
|
2014
2689
|
ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS,
|
|
2015
2690
|
ENVIRONMENT_PROVIDER_ID_PATTERN,
|
|
2016
2691
|
ENVIRONMENT_PROVIDER_REQUIREMENT_NAMES,
|
|
2017
2692
|
KV_VALUE_MAX_BYTES,
|
|
2018
2693
|
MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS,
|
|
2019
|
-
MENTION_PROVIDER_ID_PATTERN,
|
|
2020
|
-
PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS,
|
|
2021
|
-
PLUGIN_AGENT_SELECTION_MAX_IDS,
|
|
2022
|
-
PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS,
|
|
2023
2694
|
PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS,
|
|
2024
|
-
PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES,
|
|
2025
|
-
PLUGIN_HTTP_METHODS,
|
|
2026
2695
|
PLUGIN_MENTION_TRIGGER_VALUES,
|
|
2027
2696
|
PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES,
|
|
2028
2697
|
PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES,
|
|
@@ -2034,40 +2703,47 @@ export {
|
|
|
2034
2703
|
PROVIDER_ID_PATTERN,
|
|
2035
2704
|
RESERVED_AGENT_TOOL_NAMES,
|
|
2036
2705
|
RESERVED_BB_CLI_COMMANDS,
|
|
2037
|
-
RPC_METHOD_PATTERN,
|
|
2038
2706
|
SERVER_DIRECT_AI_SERVICE_IDS,
|
|
2039
2707
|
SETTING_KEY_PATTERN,
|
|
2040
2708
|
adoptHttpRouteResponse,
|
|
2041
|
-
agentToolIconRefusalMessage,
|
|
2042
2709
|
aiServiceAlreadyRegisteredMessage,
|
|
2043
2710
|
assertAiServiceRegistrable,
|
|
2044
|
-
|
|
2711
|
+
coerceStoredPluginSettingValue,
|
|
2045
2712
|
deriveValidatedProviderOptions,
|
|
2046
2713
|
enforcePluginCliOutputLimit,
|
|
2047
2714
|
environmentCompositionSchema,
|
|
2048
2715
|
isPluginMentionTrigger,
|
|
2049
2716
|
isStandardSchema,
|
|
2050
|
-
|
|
2051
|
-
|
|
2717
|
+
normalizeAgentToolRegistration,
|
|
2718
|
+
normalizeCliRegistration,
|
|
2719
|
+
normalizeHttpRouteRegistration,
|
|
2720
|
+
normalizeInteractionRequest,
|
|
2721
|
+
normalizeMentionProviderRegistration,
|
|
2722
|
+
normalizePluginAgentConfiguration,
|
|
2723
|
+
normalizeRealtimePayload,
|
|
2724
|
+
normalizeRpcJsonResult,
|
|
2725
|
+
normalizeRpcRegistration,
|
|
2726
|
+
normalizeWebSocketRouteRegistration,
|
|
2052
2727
|
parsePluginAgentToolPresentation,
|
|
2053
2728
|
pluginCliCollisionWarning,
|
|
2054
2729
|
pluginHookAlreadyRegisteredMessage,
|
|
2055
2730
|
providerAlreadyRegisteredMessage,
|
|
2056
2731
|
providerIconRefusalMessage,
|
|
2057
2732
|
providerWithoutBridgeMessage,
|
|
2058
|
-
readRpcMethodContract,
|
|
2059
2733
|
registerSettingDescriptors,
|
|
2060
|
-
|
|
2734
|
+
runPluginStorageMigrations,
|
|
2061
2735
|
storePluginHook,
|
|
2062
|
-
summarizeParseIssues,
|
|
2063
2736
|
summarizeStandardIssues,
|
|
2064
2737
|
undeclaredIconProblem,
|
|
2738
|
+
validateBackgroundServiceRegistration,
|
|
2065
2739
|
validatePluginAiServiceDeclaration,
|
|
2066
2740
|
validatePluginEnvironmentProviderDeclaration,
|
|
2067
2741
|
validatePluginMachineProviderDeclaration,
|
|
2068
2742
|
validatePluginProviderDeclaration,
|
|
2069
2743
|
validatePluginProviderEnvEntries,
|
|
2744
|
+
validateProviderEnvContribution,
|
|
2745
|
+
validateRpcValue,
|
|
2746
|
+
validateScheduleRegistration,
|
|
2070
2747
|
validateServerAccessProviderDeclaration,
|
|
2071
|
-
validateSettingsUpdate
|
|
2072
|
-
zodSchemaToJsonSchema
|
|
2748
|
+
validateSettingsUpdate
|
|
2073
2749
|
};
|