@openclaw/plugin-inspector 0.1.0 → 0.1.2
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/CHANGELOG.md +17 -0
- package/README.md +81 -138
- package/examples/github-actions-plugin-inspector.yml +2 -2
- package/examples/plugin-inspector.config.json +3 -0
- package/package.json +3 -2
- package/src/advanced.js +195 -0
- package/src/api.js +91 -0
- package/src/capture-api.js +180 -12
- package/src/cli.js +69 -27
- package/src/config.js +16 -2
- package/src/index.js +9 -184
- package/src/init.js +150 -0
- package/src/inspector.js +89 -14
- package/src/mock-sdk-capture-runner.js +110 -17
- package/src/runtime-capture-report.js +11 -1
- package/src/sdk-mock.js +1266 -13
- package/src/synthetic-probes.js +153 -9
package/src/sdk-mock.js
CHANGED
|
@@ -1,10 +1,227 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const SOURCE_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"]);
|
|
5
|
+
const SKIP_DIRS = new Set([".git", "coverage", "node_modules", "reports"]);
|
|
6
|
+
|
|
7
|
+
export const mockSdkSubpathExports = {
|
|
8
|
+
"plugin-entry": [
|
|
9
|
+
"buildPluginConfigSchema",
|
|
10
|
+
"definePluginEntry",
|
|
11
|
+
"emptyPluginConfigSchema",
|
|
12
|
+
],
|
|
13
|
+
core: [
|
|
14
|
+
"buildChannelConfigSchema",
|
|
15
|
+
"buildPluginConfigSchema",
|
|
16
|
+
"createChatChannelPlugin",
|
|
17
|
+
"createDedupeCache",
|
|
18
|
+
"defineChannelPluginEntry",
|
|
19
|
+
"definePluginEntry",
|
|
20
|
+
"defineSetupPluginEntry",
|
|
21
|
+
"emptyChannelConfigSchema",
|
|
22
|
+
"emptyPluginConfigSchema",
|
|
23
|
+
"jsonResult",
|
|
24
|
+
"readNumberParam",
|
|
25
|
+
"readStringParam",
|
|
26
|
+
],
|
|
27
|
+
"channel-core": [
|
|
28
|
+
"buildChannelConfigSchema",
|
|
29
|
+
"buildThreadAwareOutboundSessionRoute",
|
|
30
|
+
"clearAccountEntryFields",
|
|
31
|
+
"createChatChannelPlugin",
|
|
32
|
+
"defineChannelPluginEntry",
|
|
33
|
+
"defineSetupPluginEntry",
|
|
34
|
+
"parseOptionalDelimitedEntries",
|
|
35
|
+
"recoverCurrentThreadSessionId",
|
|
36
|
+
"stripChannelTargetPrefix",
|
|
37
|
+
"stripTargetKindPrefix",
|
|
38
|
+
"tryReadSecretFileSync",
|
|
39
|
+
],
|
|
40
|
+
"webhook-ingress": [
|
|
41
|
+
"applyBasicWebhookRequestGuards",
|
|
42
|
+
"beginWebhookRequestPipelineOrReject",
|
|
43
|
+
"createAuthRateLimiter",
|
|
44
|
+
"createWebhookInFlightLimiter",
|
|
45
|
+
"isJsonContentType",
|
|
46
|
+
"isRequestBodyLimitError",
|
|
47
|
+
"normalizePluginHttpPath",
|
|
48
|
+
"normalizeWebhookPath",
|
|
49
|
+
"readJsonWebhookBodyOrReject",
|
|
50
|
+
"readRequestBodyWithLimit",
|
|
51
|
+
"readWebhookBodyOrReject",
|
|
52
|
+
"registerPluginHttpRoute",
|
|
53
|
+
"registerWebhookTarget",
|
|
54
|
+
"registerWebhookTargetWithPluginRoute",
|
|
55
|
+
"requestBodyErrorToText",
|
|
56
|
+
"resolveRequestClientIp",
|
|
57
|
+
"resolveSingleWebhookTarget",
|
|
58
|
+
"resolveSingleWebhookTargetAsync",
|
|
59
|
+
"resolveWebhookPath",
|
|
60
|
+
"resolveWebhookTargetWithAuthOrReject",
|
|
61
|
+
"resolveWebhookTargetWithAuthOrRejectSync",
|
|
62
|
+
"resolveWebhookTargets",
|
|
63
|
+
"withResolvedWebhookRequestPipeline",
|
|
64
|
+
],
|
|
65
|
+
"provider-entry": [
|
|
66
|
+
"buildSingleProviderApiKeyCatalog",
|
|
67
|
+
"createProviderApiKeyAuthMethod",
|
|
68
|
+
"defineSingleProviderPluginEntry",
|
|
69
|
+
],
|
|
70
|
+
"provider-auth": ["createProviderApiKeyAuthMethod"],
|
|
71
|
+
"provider-auth-runtime": ["createProviderApiKeyAuthMethod"],
|
|
72
|
+
"provider-http": [
|
|
73
|
+
"assertOkOrThrowHttpError",
|
|
74
|
+
"assertOkOrThrowProviderError",
|
|
75
|
+
"createProviderHttpError",
|
|
76
|
+
"extractProviderErrorDetail",
|
|
77
|
+
"extractProviderRequestId",
|
|
78
|
+
"fetchWithTimeout",
|
|
79
|
+
"formatProviderErrorPayload",
|
|
80
|
+
"formatProviderHttpErrorMessage",
|
|
81
|
+
"normalizeBaseUrl",
|
|
82
|
+
"postJsonRequest",
|
|
83
|
+
"readResponseTextLimited",
|
|
84
|
+
"resolveProviderEndpoint",
|
|
85
|
+
"resolveProviderRequestCapabilities",
|
|
86
|
+
"resolveProviderRequestPolicy",
|
|
87
|
+
"truncateErrorDetail",
|
|
88
|
+
],
|
|
89
|
+
"provider-model-shared": [
|
|
90
|
+
"ANTHROPIC_BY_MODEL_REPLAY_HOOKS",
|
|
91
|
+
"DEFAULT_CONTEXT_TOKENS",
|
|
92
|
+
"NATIVE_ANTHROPIC_REPLAY_HOOKS",
|
|
93
|
+
"OPENAI_COMPATIBLE_REPLAY_HOOKS",
|
|
94
|
+
"PASSTHROUGH_GEMINI_REPLAY_HOOKS",
|
|
95
|
+
"buildProviderReplayFamilyHooks",
|
|
96
|
+
"getModelProviderHint",
|
|
97
|
+
"isProxyReasoningUnsupportedModelHint",
|
|
98
|
+
"normalizeProviderId",
|
|
99
|
+
"resolveProviderEndpoint",
|
|
100
|
+
],
|
|
101
|
+
"provider-stream": [
|
|
102
|
+
"GOOGLE_THINKING_STREAM_HOOKS",
|
|
103
|
+
"KILOCODE_THINKING_STREAM_HOOKS",
|
|
104
|
+
"MINIMAX_FAST_MODE_STREAM_HOOKS",
|
|
105
|
+
"MOONSHOT_THINKING_STREAM_HOOKS",
|
|
106
|
+
"OPENAI_RESPONSES_STREAM_HOOKS",
|
|
107
|
+
"OPENROUTER_THINKING_STREAM_HOOKS",
|
|
108
|
+
"TOOL_STREAM_DEFAULT_ON_HOOKS",
|
|
109
|
+
"buildProviderStreamFamilyHooks",
|
|
110
|
+
],
|
|
111
|
+
"provider-stream-shared": ["buildProviderStreamFamilyHooks"],
|
|
112
|
+
"provider-tools": [
|
|
113
|
+
"GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS",
|
|
114
|
+
"HTML_ENTITY_TOOL_CALL_ARGUMENTS_ENCODING",
|
|
115
|
+
"XAI_TOOL_SCHEMA_PROFILE",
|
|
116
|
+
"XAI_UNSUPPORTED_SCHEMA_KEYWORDS",
|
|
117
|
+
"applyXaiModelCompat",
|
|
118
|
+
"buildProviderToolCompatFamilyHooks",
|
|
119
|
+
"cleanSchemaForGemini",
|
|
120
|
+
"findOpenAIStrictSchemaViolations",
|
|
121
|
+
"findUnsupportedSchemaKeywords",
|
|
122
|
+
"inspectGeminiToolSchemas",
|
|
123
|
+
"inspectOpenAIToolSchemas",
|
|
124
|
+
"normalizeGeminiToolSchemas",
|
|
125
|
+
"normalizeOpenAIToolSchemas",
|
|
126
|
+
"resolveXaiModelCompatPatch",
|
|
127
|
+
"stripUnsupportedSchemaKeywords",
|
|
128
|
+
"stripXaiUnsupportedKeywords",
|
|
129
|
+
],
|
|
130
|
+
"provider-web-search": ["createWebSearchProviderContractFields", "jsonResult", "readStringParam"],
|
|
131
|
+
"provider-web-search-config-contract": [
|
|
132
|
+
"createWebSearchProviderContractFields",
|
|
133
|
+
"getScopedCredentialValue",
|
|
134
|
+
"getTopLevelCredentialValue",
|
|
135
|
+
"mergeScopedSearchConfig",
|
|
136
|
+
"resolveProviderWebSearchPluginConfig",
|
|
137
|
+
"setProviderWebSearchPluginConfigValue",
|
|
138
|
+
"setScopedCredentialValue",
|
|
139
|
+
"setTopLevelCredentialValue",
|
|
140
|
+
],
|
|
141
|
+
"runtime-env": ["createRuntimeEnv", "resolveRuntimeEnv"],
|
|
142
|
+
"runtime-logger": ["createLoggerBackedRuntime", "createSubsystemLogger"],
|
|
143
|
+
"config-runtime": [
|
|
144
|
+
"buildPluginConfigSchema",
|
|
145
|
+
"emptyPluginConfigSchema",
|
|
146
|
+
"isSecretRef",
|
|
147
|
+
"normalizeSecretInputString",
|
|
148
|
+
],
|
|
149
|
+
"plugin-runtime": ["createLoggerBackedRuntime", "createSubsystemLogger"],
|
|
150
|
+
"secret-input": [
|
|
151
|
+
"buildOptionalSecretInputSchema",
|
|
152
|
+
"buildSecretInputArraySchema",
|
|
153
|
+
"buildSecretInputSchema",
|
|
154
|
+
"coerceSecretRef",
|
|
155
|
+
"hasConfiguredSecretInput",
|
|
156
|
+
"isSecretRef",
|
|
157
|
+
"normalizeResolvedSecretInputString",
|
|
158
|
+
"normalizeSecretInput",
|
|
159
|
+
"normalizeSecretInputString",
|
|
160
|
+
"resolveSecretInputString",
|
|
161
|
+
],
|
|
162
|
+
"security-runtime": [
|
|
163
|
+
"buildHostnameAllowlistPolicyFromSuffixAllowlist",
|
|
164
|
+
"fetchWithSsrFGuard",
|
|
165
|
+
"formatErrorMessage",
|
|
166
|
+
"generateSecureToken",
|
|
167
|
+
"hasConfiguredSecretInput",
|
|
168
|
+
"isBlockedHostnameOrIp",
|
|
169
|
+
"isPrivateNetworkOptInEnabled",
|
|
170
|
+
"mergeSsrFPolicies",
|
|
171
|
+
"redactSensitiveText",
|
|
172
|
+
"safeEqualSecret",
|
|
173
|
+
"wrapExternalContent",
|
|
174
|
+
],
|
|
175
|
+
"ssrf-runtime": [
|
|
176
|
+
"SsrFBlockedError",
|
|
177
|
+
"closeDispatcher",
|
|
178
|
+
"createPinnedDispatcher",
|
|
179
|
+
"fetchWithSsrFGuard",
|
|
180
|
+
"formatErrorMessage",
|
|
181
|
+
"isBlockedHostnameOrIp",
|
|
182
|
+
"isPrivateOrLoopbackHost",
|
|
183
|
+
"resolvePinnedHostname",
|
|
184
|
+
"resolvePinnedHostnameWithPolicy",
|
|
185
|
+
"ssrfPolicyFromHttpBaseUrlAllowedHostname",
|
|
186
|
+
],
|
|
187
|
+
"browser-security-runtime": [
|
|
188
|
+
"SafeOpenError",
|
|
189
|
+
"SsrFBlockedError",
|
|
190
|
+
"extractErrorCode",
|
|
191
|
+
"formatErrorMessage",
|
|
192
|
+
"generateSecureToken",
|
|
193
|
+
"hasProxyEnvConfigured",
|
|
194
|
+
"isBlockedHostnameOrIp",
|
|
195
|
+
"isNotFoundPathError",
|
|
196
|
+
"isPathInside",
|
|
197
|
+
"normalizeHostname",
|
|
198
|
+
"openFileWithinRoot",
|
|
199
|
+
"redactSensitiveText",
|
|
200
|
+
"safeEqualSecret",
|
|
201
|
+
"wrapExternalContent",
|
|
202
|
+
"writeFileFromPathWithinRoot",
|
|
203
|
+
],
|
|
204
|
+
"media-runtime": ["renderQrTerminal"],
|
|
205
|
+
"text-runtime": ["chunkTextForOutbound", "truncateText"],
|
|
206
|
+
testing: ["createMockPluginRuntime"],
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const mockSdkExportNames = [
|
|
210
|
+
...new Set([
|
|
211
|
+
"createPlugin",
|
|
212
|
+
"definePlugin",
|
|
213
|
+
"pluginSdkMock",
|
|
214
|
+
...Object.values(mockSdkSubpathExports).flat(),
|
|
215
|
+
]),
|
|
216
|
+
].sort();
|
|
217
|
+
|
|
218
|
+
export async function createMockSdkPackage(rootDir, options = {}) {
|
|
5
219
|
const packageDir = path.join(rootDir, "node_modules", "openclaw");
|
|
6
220
|
const pluginSdkDir = path.join(packageDir, "plugin-sdk");
|
|
221
|
+
const externalDir = path.join(rootDir, "mock-modules", "external");
|
|
7
222
|
await mkdir(pluginSdkDir, { recursive: true });
|
|
223
|
+
await mkdir(externalDir, { recursive: true });
|
|
224
|
+
const imports = options.pluginRoot ? await collectRuntimeImports(options.pluginRoot) : emptyRuntimeImports();
|
|
8
225
|
await writeFile(
|
|
9
226
|
path.join(packageDir, "package.json"),
|
|
10
227
|
`${JSON.stringify(
|
|
@@ -14,7 +231,7 @@ export async function createMockSdkPackage(rootDir) {
|
|
|
14
231
|
type: "module",
|
|
15
232
|
exports: {
|
|
16
233
|
"./plugin-sdk": "./plugin-sdk/index.js",
|
|
17
|
-
"./plugin-sdk/*": "./plugin-sdk
|
|
234
|
+
"./plugin-sdk/*": "./plugin-sdk/*.js",
|
|
18
235
|
},
|
|
19
236
|
},
|
|
20
237
|
null,
|
|
@@ -23,20 +240,498 @@ export async function createMockSdkPackage(rootDir) {
|
|
|
23
240
|
"utf8",
|
|
24
241
|
);
|
|
25
242
|
await writeFile(path.join(pluginSdkDir, "index.js"), mockSdkSource(), "utf8");
|
|
26
|
-
|
|
243
|
+
for (const [subpath, exportNames] of Object.entries(mockSdkSubpathExports)) {
|
|
244
|
+
await writeFile(path.join(pluginSdkDir, `${subpath}.js`), mockSdkSubpathSource(exportNames), "utf8");
|
|
245
|
+
}
|
|
246
|
+
for (const specifier of imports.openclawSdkSpecifiers) {
|
|
247
|
+
if (specifier === "openclaw/plugin-sdk") {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const relative = specifier.slice("openclaw/plugin-sdk/".length);
|
|
251
|
+
if (mockSdkSubpathExports[relative]) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const targetPath = path.join(pluginSdkDir, `${relative}.js`);
|
|
255
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
256
|
+
await writeFile(
|
|
257
|
+
targetPath,
|
|
258
|
+
dynamicMockModuleSource(imports.bySpecifier.get(specifier) ?? new Set(), {
|
|
259
|
+
includeSdkRuntime: true,
|
|
260
|
+
zod: relative === "zod",
|
|
261
|
+
}),
|
|
262
|
+
"utf8",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const externalMap = {};
|
|
267
|
+
for (const specifier of imports.bareSpecifiers) {
|
|
268
|
+
const fileName = `${safeModuleFileName(specifier)}.js`;
|
|
269
|
+
externalMap[specifier] = path.join(externalDir, fileName);
|
|
270
|
+
await writeFile(
|
|
271
|
+
path.join(externalDir, fileName),
|
|
272
|
+
externalMockModuleSource(specifier, imports.bySpecifier.get(specifier) ?? new Set()),
|
|
273
|
+
"utf8",
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const fallbackExternalPath = path.join(externalDir, "__fallback__.js");
|
|
278
|
+
await writeFile(fallbackExternalPath, externalMockModuleSource("__fallback__", new Set()), "utf8");
|
|
279
|
+
const loaderPath = path.join(rootDir, "mock-loader.mjs");
|
|
280
|
+
await writeFile(
|
|
281
|
+
loaderPath,
|
|
282
|
+
mockLoaderSource({
|
|
283
|
+
externalMap,
|
|
284
|
+
fallbackExternalPath,
|
|
285
|
+
pluginSdkDir,
|
|
286
|
+
}),
|
|
287
|
+
"utf8",
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
return { packageDir, loaderPath, pluginSdkDir };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function emptyRuntimeImports() {
|
|
294
|
+
return {
|
|
295
|
+
bySpecifier: new Map(),
|
|
296
|
+
openclawSdkSpecifiers: new Set(["openclaw/plugin-sdk"]),
|
|
297
|
+
bareSpecifiers: new Set(),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function collectRuntimeImports(pluginRoot) {
|
|
302
|
+
const bySpecifier = new Map();
|
|
303
|
+
const openclawSdkSpecifiers = new Set(["openclaw/plugin-sdk"]);
|
|
304
|
+
const bareSpecifiers = new Set();
|
|
305
|
+
for (const filePath of await listSourceFiles(pluginRoot)) {
|
|
306
|
+
const text = await readFile(filePath, "utf8");
|
|
307
|
+
for (const entry of parseModuleImports(text)) {
|
|
308
|
+
if (entry.specifier.startsWith("openclaw/plugin-sdk")) {
|
|
309
|
+
openclawSdkSpecifiers.add(entry.specifier);
|
|
310
|
+
} else if (isMockableBareSpecifier(entry.specifier)) {
|
|
311
|
+
bareSpecifiers.add(entry.specifier);
|
|
312
|
+
} else {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const names = bySpecifier.get(entry.specifier) ?? new Set();
|
|
316
|
+
for (const name of entry.names) {
|
|
317
|
+
names.add(name);
|
|
318
|
+
}
|
|
319
|
+
bySpecifier.set(entry.specifier, names);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return { bySpecifier, openclawSdkSpecifiers, bareSpecifiers };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function listSourceFiles(dir) {
|
|
326
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
327
|
+
const files = [];
|
|
328
|
+
for (const entry of entries) {
|
|
329
|
+
if (entry.name.startsWith(".") && entry.name !== ".clawhub") {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const fullPath = path.join(dir, entry.name);
|
|
333
|
+
if (entry.isDirectory()) {
|
|
334
|
+
if (!SKIP_DIRS.has(entry.name)) {
|
|
335
|
+
files.push(...(await listSourceFiles(fullPath)));
|
|
336
|
+
}
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
|
|
340
|
+
files.push(fullPath);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return files;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function parseModuleImports(text) {
|
|
347
|
+
const entries = [];
|
|
348
|
+
const patterns = [
|
|
349
|
+
/\bimport\s+([\s\S]*?)\s+from\s+["']([^"']+)["']/g,
|
|
350
|
+
/\bexport\s+(?:type\s+)?(?:\*\s+from|\{([\s\S]*?)\}\s+from)\s+["']([^"']+)["']/g,
|
|
351
|
+
];
|
|
352
|
+
for (const pattern of patterns) {
|
|
353
|
+
for (const match of text.matchAll(pattern)) {
|
|
354
|
+
const specifier = match[2];
|
|
355
|
+
if (specifier) {
|
|
356
|
+
entries.push({ specifier, names: parseNamedImports(match[1] ?? "") });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const match of text.matchAll(/\bimport\s+["']([^"']+)["']/g)) {
|
|
361
|
+
entries.push({ specifier: match[1], names: new Set() });
|
|
362
|
+
}
|
|
363
|
+
return entries;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function parseNamedImports(clause) {
|
|
367
|
+
const names = new Set();
|
|
368
|
+
const named = /\{([\s\S]*?)\}/.exec(clause)?.[1] ?? clause;
|
|
369
|
+
for (const rawPart of named.split(",")) {
|
|
370
|
+
const part = rawPart.trim();
|
|
371
|
+
if (!part || part.startsWith("type ")) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const sourceName = part.replace(/^type\s+/u, "").split(/\s+as\s+/u)[0]?.trim();
|
|
375
|
+
if (sourceName && /^[A-Za-z_$][\w$]*$/u.test(sourceName)) {
|
|
376
|
+
names.add(sourceName);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return names;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function isMockableBareSpecifier(specifier) {
|
|
383
|
+
return (
|
|
384
|
+
!specifier.startsWith(".") &&
|
|
385
|
+
!specifier.startsWith("/") &&
|
|
386
|
+
!specifier.startsWith("node:") &&
|
|
387
|
+
!specifier.startsWith("data:") &&
|
|
388
|
+
!specifier.startsWith("file:")
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function safeModuleFileName(specifier) {
|
|
393
|
+
return specifier.replace(/[^A-Za-z0-9._-]+/gu, "__");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function mockLoaderSource({ externalMap, fallbackExternalPath, pluginSdkDir }) {
|
|
397
|
+
return `import { existsSync } from "node:fs";
|
|
398
|
+
import { readFile } from "node:fs/promises";
|
|
399
|
+
import { builtinModules, stripTypeScriptTypes } from "node:module";
|
|
400
|
+
import path from "node:path";
|
|
401
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
402
|
+
|
|
403
|
+
const externalMap = new Map(Object.entries(${JSON.stringify(externalMap)}));
|
|
404
|
+
const fallbackExternalPath = ${JSON.stringify(fallbackExternalPath)};
|
|
405
|
+
const pluginSdkDir = ${JSON.stringify(pluginSdkDir)};
|
|
406
|
+
const builtins = new Set([...builtinModules, ...builtinModules.map((name) => \`node:\${name}\`)]);
|
|
407
|
+
|
|
408
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
409
|
+
if (specifier === "openclaw/plugin-sdk") {
|
|
410
|
+
return moduleUrl(path.join(pluginSdkDir, "index.js"));
|
|
411
|
+
}
|
|
412
|
+
if (specifier.startsWith("openclaw/plugin-sdk/")) {
|
|
413
|
+
const subpath = specifier.slice("openclaw/plugin-sdk/".length);
|
|
414
|
+
return moduleUrl(path.join(pluginSdkDir, \`\${subpath}.js\`));
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
return await nextResolve(specifier, context);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
const resolved = resolveExtensionless(specifier, context.parentURL);
|
|
420
|
+
if (resolved) {
|
|
421
|
+
return moduleUrl(resolved);
|
|
422
|
+
}
|
|
423
|
+
if (isMockableBareSpecifier(specifier)) {
|
|
424
|
+
return moduleUrl(externalMap.get(specifier) ?? fallbackExternalPath);
|
|
425
|
+
}
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export async function load(url, context, nextLoad) {
|
|
431
|
+
if (url.startsWith("file:") && /\\.[cm]?ts$/u.test(fileURLToPath(url))) {
|
|
432
|
+
const rawSource = await readFile(fileURLToPath(url), "utf8");
|
|
433
|
+
return { format: "module", source: stripTypeScriptTypes(rawSource, { mode: "transform" }), shortCircuit: true };
|
|
434
|
+
}
|
|
435
|
+
return nextLoad(url, context);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function moduleUrl(filePath) {
|
|
439
|
+
return { url: pathToFileURL(filePath).href, shortCircuit: true };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function resolveExtensionless(specifier, parentURL) {
|
|
443
|
+
if (!parentURL || (!specifier.startsWith(".") && !specifier.startsWith("/"))) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
const parentDir = path.dirname(fileURLToPath(parentURL));
|
|
447
|
+
const base = specifier.startsWith("/") ? specifier : path.resolve(parentDir, specifier);
|
|
448
|
+
const parsed = path.parse(base);
|
|
449
|
+
const withoutJsExtension = [".js", ".mjs", ".cjs"].includes(parsed.ext) ? path.join(parsed.dir, parsed.name) : null;
|
|
450
|
+
const candidates = [
|
|
451
|
+
base,
|
|
452
|
+
...(withoutJsExtension ? [\`\${withoutJsExtension}.ts\`, \`\${withoutJsExtension}.mts\`, \`\${withoutJsExtension}.cts\`] : []),
|
|
453
|
+
\`\${base}.js\`,
|
|
454
|
+
\`\${base}.mjs\`,
|
|
455
|
+
\`\${base}.ts\`,
|
|
456
|
+
path.join(base, "index.js"),
|
|
457
|
+
path.join(base, "index.mjs"),
|
|
458
|
+
path.join(base, "index.ts"),
|
|
459
|
+
];
|
|
460
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function isMockableBareSpecifier(specifier) {
|
|
464
|
+
return !builtins.has(specifier) &&
|
|
465
|
+
!specifier.startsWith(".") &&
|
|
466
|
+
!specifier.startsWith("/") &&
|
|
467
|
+
!specifier.startsWith("data:") &&
|
|
468
|
+
!specifier.startsWith("file:");
|
|
469
|
+
}
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function dynamicMockModuleSource(exportNames, options = {}) {
|
|
474
|
+
const names = new Set([...exportNames].filter(isValidExportName));
|
|
475
|
+
if (options.zod) {
|
|
476
|
+
addZodExports(names);
|
|
477
|
+
}
|
|
478
|
+
return `${genericMockRuntimeSource(options)}
|
|
479
|
+
${[...names].map(genericExportStatement).join("\n")}
|
|
480
|
+
|
|
481
|
+
export default ${options.zod ? "createZNamespace()" : 'createMockValue("default")'};
|
|
482
|
+
`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function externalMockModuleSource(specifier, exportNames) {
|
|
486
|
+
const names = new Set([...exportNames].filter(isValidExportName));
|
|
487
|
+
if (specifier === "zod") {
|
|
488
|
+
addZodExports(names);
|
|
489
|
+
}
|
|
490
|
+
return dynamicMockModuleSource(names, { zod: specifier === "zod" });
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function addZodExports(names) {
|
|
494
|
+
for (const name of ["z", "any", "array", "boolean", "enum", "literal", "number", "object", "record", "string", "unknown"]) {
|
|
495
|
+
names.add(name);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function isValidExportName(name) {
|
|
500
|
+
return name !== "default" && /^[A-Za-z_$][\w$]*$/u.test(name);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function genericExportStatement(name) {
|
|
504
|
+
if (name === "z") {
|
|
505
|
+
return "export const z = createZNamespace();";
|
|
506
|
+
}
|
|
507
|
+
if (name === "Type") {
|
|
508
|
+
return "export const Type = createTypeNamespace();";
|
|
509
|
+
}
|
|
510
|
+
if (["any", "array", "boolean", "enum", "literal", "number", "object", "record", "string", "unknown"].includes(name)) {
|
|
511
|
+
if (name === "enum") {
|
|
512
|
+
return "const zodEnum = createZNamespace().enum;\nexport { zodEnum as enum };";
|
|
513
|
+
}
|
|
514
|
+
return `export const ${name} = createZNamespace().${name};`;
|
|
515
|
+
}
|
|
516
|
+
if (["createChatChannelPlugin", "createPlugin", "defineChannelPluginEntry", "definePlugin", "definePluginEntry", "defineSetupPluginEntry"].includes(name)) {
|
|
517
|
+
return name === "definePluginEntry" ? "export { definePluginEntry };" : `export const ${name} = definePluginEntry;`;
|
|
518
|
+
}
|
|
519
|
+
if (/^[A-Z].*Schema$/u.test(name)) {
|
|
520
|
+
return `export const ${name} = createSchema();`;
|
|
521
|
+
}
|
|
522
|
+
return `export const ${name} = createMockValue(${JSON.stringify(name)});`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function genericMockRuntimeSource(options = {}) {
|
|
526
|
+
return `${options.includeSdkRuntime ? `function definePluginEntry(entry) {
|
|
527
|
+
if (entry && typeof entry === "object" && typeof entry.register === "function") {
|
|
528
|
+
return entry;
|
|
529
|
+
}
|
|
530
|
+
if (entry && typeof entry === "object" && typeof entry.registerFull === "function") {
|
|
531
|
+
return { ...entry, register: entry.registerFull };
|
|
532
|
+
}
|
|
533
|
+
return typeof entry === "function" ? { register: entry } : entry;
|
|
534
|
+
}
|
|
535
|
+
` : ""}
|
|
536
|
+
function createMockValue(name) {
|
|
537
|
+
function fn(...args) {
|
|
538
|
+
if (name.startsWith("normalize")) {
|
|
539
|
+
return typeof args[0] === "string" ? args[0] : "";
|
|
540
|
+
}
|
|
541
|
+
if (name === "jsonResult") {
|
|
542
|
+
return { type: "json", value: args[0] };
|
|
543
|
+
}
|
|
544
|
+
if (name === "readStringParam") {
|
|
545
|
+
return typeof args[0] === "string" ? args[0] : "";
|
|
546
|
+
}
|
|
547
|
+
return createMockValue(name);
|
|
548
|
+
}
|
|
549
|
+
return new Proxy(fn, {
|
|
550
|
+
get(_target, property) {
|
|
551
|
+
if (property === "then") {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
if (property === Symbol.toPrimitive) {
|
|
555
|
+
return () => name;
|
|
556
|
+
}
|
|
557
|
+
if (property === "toString") {
|
|
558
|
+
return () => name;
|
|
559
|
+
}
|
|
560
|
+
if (property === "valueOf") {
|
|
561
|
+
return () => name;
|
|
562
|
+
}
|
|
563
|
+
return createMockValue(\`\${name}.\${String(property)}\`);
|
|
564
|
+
},
|
|
565
|
+
construct() {
|
|
566
|
+
return createMockValue(name);
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function createZNamespace() {
|
|
572
|
+
const namespace = {
|
|
573
|
+
any: () => createSchema(),
|
|
574
|
+
array: () => createSchema([]),
|
|
575
|
+
boolean: () => createSchema(),
|
|
576
|
+
enum: (values) => createSchema(Array.isArray(values) ? values[0] : undefined),
|
|
577
|
+
literal: (value) => createSchema(value),
|
|
578
|
+
number: () => createSchema(),
|
|
579
|
+
object: (shape = {}) => createSchema(undefined, shape),
|
|
580
|
+
record: () => createSchema({}),
|
|
581
|
+
string: () => createSchema(),
|
|
582
|
+
unknown: () => createSchema(),
|
|
583
|
+
};
|
|
584
|
+
return new Proxy(namespace, {
|
|
585
|
+
get(target, property) {
|
|
586
|
+
if (property in target) {
|
|
587
|
+
return target[property];
|
|
588
|
+
}
|
|
589
|
+
return () => createSchema();
|
|
590
|
+
},
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function createSchema(defaultValue, shape) {
|
|
595
|
+
const schema = {
|
|
596
|
+
parse(value) {
|
|
597
|
+
if (shape && isPlainObject(value ?? defaultValue ?? {})) {
|
|
598
|
+
const source = isPlainObject(value) ? value : {};
|
|
599
|
+
const output = isPlainObject(defaultValue) ? clonePlain(defaultValue) : {};
|
|
600
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
601
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
602
|
+
output[key] = parseWithSchema(fieldSchema, source[key]);
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
const parsed = parseWithSchema(fieldSchema, undefined);
|
|
606
|
+
if (parsed !== undefined) {
|
|
607
|
+
output[key] = parsed;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return output;
|
|
611
|
+
}
|
|
612
|
+
return value === undefined ? clonePlain(defaultValue) : value;
|
|
613
|
+
},
|
|
614
|
+
default(value) {
|
|
615
|
+
return createSchema(value, shape);
|
|
616
|
+
},
|
|
617
|
+
optional() {
|
|
618
|
+
return this;
|
|
619
|
+
},
|
|
620
|
+
nullable() {
|
|
621
|
+
return this;
|
|
622
|
+
},
|
|
623
|
+
nullish() {
|
|
624
|
+
return this;
|
|
625
|
+
},
|
|
626
|
+
strict() {
|
|
627
|
+
return this;
|
|
628
|
+
},
|
|
629
|
+
passthrough() {
|
|
630
|
+
return this;
|
|
631
|
+
},
|
|
632
|
+
regex() {
|
|
633
|
+
return this;
|
|
634
|
+
},
|
|
635
|
+
min() {
|
|
636
|
+
return this;
|
|
637
|
+
},
|
|
638
|
+
max() {
|
|
639
|
+
return this;
|
|
640
|
+
},
|
|
641
|
+
int() {
|
|
642
|
+
return this;
|
|
643
|
+
},
|
|
644
|
+
positive() {
|
|
645
|
+
return this;
|
|
646
|
+
},
|
|
647
|
+
nonnegative() {
|
|
648
|
+
return this;
|
|
649
|
+
},
|
|
650
|
+
url() {
|
|
651
|
+
return this;
|
|
652
|
+
},
|
|
653
|
+
describe() {
|
|
654
|
+
return this;
|
|
655
|
+
},
|
|
656
|
+
refine() {
|
|
657
|
+
return this;
|
|
658
|
+
},
|
|
659
|
+
superRefine() {
|
|
660
|
+
return this;
|
|
661
|
+
},
|
|
662
|
+
transform() {
|
|
663
|
+
return this;
|
|
664
|
+
},
|
|
665
|
+
};
|
|
666
|
+
return new Proxy(schema, {
|
|
667
|
+
get(target, property) {
|
|
668
|
+
if (property in target) {
|
|
669
|
+
return target[property];
|
|
670
|
+
}
|
|
671
|
+
return () => target;
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function parseWithSchema(schema, value) {
|
|
677
|
+
return schema && typeof schema.parse === "function" ? schema.parse(value) : value;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function clonePlain(value) {
|
|
681
|
+
if (value === undefined || value === null) {
|
|
682
|
+
return value;
|
|
683
|
+
}
|
|
684
|
+
return JSON.parse(JSON.stringify(value));
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function isPlainObject(value) {
|
|
688
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function createTypeNamespace() {
|
|
692
|
+
const namespace = {
|
|
693
|
+
Any: () => ({}),
|
|
694
|
+
Array: (items = {}) => ({ type: "array", items }),
|
|
695
|
+
Boolean: () => ({ type: "boolean" }),
|
|
696
|
+
Literal: (value) => ({ const: value }),
|
|
697
|
+
Number: () => ({ type: "number" }),
|
|
698
|
+
Object: (properties = {}) => ({ type: "object", properties }),
|
|
699
|
+
Optional: (schema) => schema,
|
|
700
|
+
String: (options = {}) => ({ type: "string", ...options }),
|
|
701
|
+
Union: (schemas = []) => ({ anyOf: schemas }),
|
|
702
|
+
Unknown: () => ({}),
|
|
703
|
+
};
|
|
704
|
+
return new Proxy(namespace, {
|
|
705
|
+
get(target, property) {
|
|
706
|
+
if (property in target) {
|
|
707
|
+
return target[property];
|
|
708
|
+
}
|
|
709
|
+
return (...args) => ({ kind: String(property), args });
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
`;
|
|
27
714
|
}
|
|
28
715
|
|
|
29
716
|
function mockSdkSource() {
|
|
30
|
-
return `
|
|
717
|
+
return `function normalizeEntry(entry) {
|
|
31
718
|
return typeof entry === "function" ? { register: entry } : entry;
|
|
32
719
|
}
|
|
33
720
|
|
|
721
|
+
export function definePluginEntry(entry) {
|
|
722
|
+
return normalizeEntry(entry);
|
|
723
|
+
}
|
|
724
|
+
|
|
34
725
|
export function defineChannelPluginEntry(entry) {
|
|
35
|
-
return
|
|
726
|
+
return normalizeEntry(entry);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
export function defineSetupPluginEntry(entry) {
|
|
730
|
+
return normalizeEntry(entry);
|
|
36
731
|
}
|
|
37
732
|
|
|
38
733
|
export function createChatChannelPlugin(entry) {
|
|
39
|
-
return
|
|
734
|
+
return normalizeEntry(entry);
|
|
40
735
|
}
|
|
41
736
|
|
|
42
737
|
export function definePlugin(entry) {
|
|
@@ -47,15 +742,573 @@ export function createPlugin(entry) {
|
|
|
47
742
|
return definePluginEntry(entry);
|
|
48
743
|
}
|
|
49
744
|
|
|
745
|
+
export function defineSingleProviderPluginEntry(options) {
|
|
746
|
+
return definePluginEntry({
|
|
747
|
+
...options,
|
|
748
|
+
register(api) {
|
|
749
|
+
if (options?.provider) {
|
|
750
|
+
api.registerProvider?.({
|
|
751
|
+
id: options.provider.id ?? options.id,
|
|
752
|
+
label: options.provider.label,
|
|
753
|
+
...options.provider,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
return options?.register?.(api);
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export function buildPluginConfigSchema(schema = {}) {
|
|
762
|
+
return schema;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export const emptyPluginConfigSchema = { type: "object", properties: {}, additionalProperties: false };
|
|
766
|
+
|
|
767
|
+
export function buildChannelConfigSchema(schema = {}) {
|
|
768
|
+
return schema;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export const emptyChannelConfigSchema = emptyPluginConfigSchema;
|
|
772
|
+
|
|
773
|
+
export function jsonResult(value) {
|
|
774
|
+
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
export function readNumberParam(value, fallback = 0) {
|
|
778
|
+
const parsed = Number(value);
|
|
779
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export function readStringParam(value, fallback = "") {
|
|
783
|
+
return typeof value === "string" ? value : fallback;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export function createDedupeCache() {
|
|
787
|
+
const seen = new Set();
|
|
788
|
+
return {
|
|
789
|
+
add(value) {
|
|
790
|
+
seen.add(value);
|
|
791
|
+
return true;
|
|
792
|
+
},
|
|
793
|
+
clear() {
|
|
794
|
+
seen.clear();
|
|
795
|
+
},
|
|
796
|
+
delete(value) {
|
|
797
|
+
return seen.delete(value);
|
|
798
|
+
},
|
|
799
|
+
has(value) {
|
|
800
|
+
return seen.has(value);
|
|
801
|
+
},
|
|
802
|
+
get size() {
|
|
803
|
+
return seen.size;
|
|
804
|
+
},
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
export function resolveGlobalDedupeCache() {
|
|
809
|
+
return createDedupeCache();
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
export function generateSecureToken() {
|
|
813
|
+
return "plugin-inspector-token";
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
export function generateSecureUuid() {
|
|
817
|
+
return "00000000-0000-4000-8000-000000000000";
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export function isSecretRef(value) {
|
|
821
|
+
return typeof value === "string" && value.startsWith("secret:");
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
export function coerceSecretRef(value) {
|
|
825
|
+
return isSecretRef(value) ? value : \`secret:\${String(value ?? "")}\`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
export function hasConfiguredSecretInput(value) {
|
|
829
|
+
return value !== undefined && value !== null && value !== "";
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
export function resolveSecretInputString(value) {
|
|
833
|
+
return typeof value === "string" ? value : value?.value ?? "";
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
export function normalizeResolvedSecretInputString(value) {
|
|
837
|
+
return resolveSecretInputString(value).trim();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
export function normalizeSecretInput(value) {
|
|
841
|
+
return value;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
export function normalizeSecretInputString(value) {
|
|
845
|
+
return String(value ?? "").trim();
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export function buildSecretInputSchema() {
|
|
849
|
+
return { type: "string" };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
export function buildOptionalSecretInputSchema() {
|
|
853
|
+
return { anyOf: [buildSecretInputSchema(), { type: "undefined" }] };
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export function buildSecretInputArraySchema() {
|
|
857
|
+
return { type: "array", items: buildSecretInputSchema() };
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
export function registerPluginHttpRoute(options = {}) {
|
|
861
|
+
return { ...options, kind: "httpRoute" };
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
export function registerWebhookTarget(options = {}) {
|
|
865
|
+
return { ...options, unregister() {} };
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
export function registerWebhookTargetWithPluginRoute(options = {}) {
|
|
869
|
+
return registerWebhookTarget({ ...options, pluginRoute: true });
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
export function resolveSingleWebhookTarget(target) {
|
|
873
|
+
return target ?? null;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export async function resolveSingleWebhookTargetAsync(target) {
|
|
877
|
+
return resolveSingleWebhookTarget(target);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
export function resolveWebhookTargetWithAuthOrReject(target) {
|
|
881
|
+
return target ?? null;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
export function resolveWebhookTargetWithAuthOrRejectSync(target) {
|
|
885
|
+
return resolveWebhookTargetWithAuthOrReject(target);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
export function resolveWebhookTargets(targets = []) {
|
|
889
|
+
return Array.isArray(targets) ? targets : [targets];
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export async function withResolvedWebhookRequestPipeline(callback) {
|
|
893
|
+
return typeof callback === "function" ? callback({}) : callback;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export function normalizeWebhookPath(value = "/") {
|
|
897
|
+
const text = String(value || "/").trim();
|
|
898
|
+
return text.startsWith("/") ? text : \`/\${text}\`;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
export function resolveWebhookPath(value = "/") {
|
|
902
|
+
return normalizeWebhookPath(typeof value === "string" ? value : value.path);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
export function normalizePluginHttpPath(value = "/") {
|
|
906
|
+
return normalizeWebhookPath(value);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export function createWebhookInFlightLimiter() {
|
|
910
|
+
return { enter: () => true, leave() {} };
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
export function applyBasicWebhookRequestGuards() {
|
|
914
|
+
return { ok: true };
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
export function beginWebhookRequestPipelineOrReject() {
|
|
918
|
+
return { ok: true };
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
export function isJsonContentType(value = "") {
|
|
922
|
+
return String(value).includes("json");
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
export function isRequestBodyLimitError(error) {
|
|
926
|
+
return error?.code === "ERR_BODY_TOO_LARGE";
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
export async function readRequestBodyWithLimit() {
|
|
930
|
+
return "";
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
export async function readJsonWebhookBodyOrReject() {
|
|
934
|
+
return {};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
export async function readWebhookBodyOrReject() {
|
|
938
|
+
return "";
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export function requestBodyErrorToText(error) {
|
|
942
|
+
return error?.message ?? String(error ?? "");
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
export function resolveRequestClientIp() {
|
|
946
|
+
return "127.0.0.1";
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
export function createAuthRateLimiter() {
|
|
950
|
+
return { check: () => true };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
export function createProviderApiKeyAuthMethod(options = {}) {
|
|
954
|
+
return { type: "apiKey", ...options };
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
export function buildSingleProviderApiKeyCatalog(options = {}) {
|
|
958
|
+
return {
|
|
959
|
+
order: "simple",
|
|
960
|
+
async run(ctx) {
|
|
961
|
+
return { provider: await options.buildProvider?.(ctx) };
|
|
962
|
+
},
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
export function buildProviderToolCompatFamilyHooks(family) {
|
|
967
|
+
return { family, hooks: [] };
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export function buildProviderReplayFamilyHooks(params) {
|
|
971
|
+
return { params, hooks: [] };
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
export function buildProviderStreamFamilyHooks(family) {
|
|
975
|
+
return { family, hooks: [] };
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
export function stripUnsupportedSchemaKeywords(schema) {
|
|
979
|
+
return schema;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
export function stripXaiUnsupportedKeywords(schema) {
|
|
983
|
+
return schema;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
export function resolveXaiModelCompatPatch() {
|
|
987
|
+
return {};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
export function applyXaiModelCompat(model) {
|
|
991
|
+
return model;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
export function findUnsupportedSchemaKeywords() {
|
|
995
|
+
return [];
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export function normalizeGeminiToolSchemas(schema) {
|
|
999
|
+
return schema;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export function inspectGeminiToolSchemas() {
|
|
1003
|
+
return [];
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
export function normalizeOpenAIToolSchemas(schema) {
|
|
1007
|
+
return schema;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
export function findOpenAIStrictSchemaViolations() {
|
|
1011
|
+
return [];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
export function inspectOpenAIToolSchemas() {
|
|
1015
|
+
return [];
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
export function cleanSchemaForGemini(schema) {
|
|
1019
|
+
return schema;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
export function getModelProviderHint() {
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
export function isProxyReasoningUnsupportedModelHint() {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
export function normalizeProviderId(value) {
|
|
1031
|
+
return String(value ?? "").trim();
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
export function resolveProviderEndpoint(value) {
|
|
1035
|
+
return value ?? null;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
export function createWebSearchProviderContractFields(fields = {}) {
|
|
1039
|
+
return fields;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
export function getScopedCredentialValue() {
|
|
1043
|
+
return undefined;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
export function getTopLevelCredentialValue() {
|
|
1047
|
+
return undefined;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
export function mergeScopedSearchConfig(config = {}) {
|
|
1051
|
+
return config;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export function resolveProviderWebSearchPluginConfig(config = {}) {
|
|
1055
|
+
return config;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
export function setProviderWebSearchPluginConfigValue(config = {}, key, value) {
|
|
1059
|
+
return { ...config, [key]: value };
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
export function setScopedCredentialValue(config = {}, key, value) {
|
|
1063
|
+
return { ...config, [key]: value };
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
export function setTopLevelCredentialValue(config = {}, key, value) {
|
|
1067
|
+
return { ...config, [key]: value };
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
export function createRuntimeEnv(env = {}) {
|
|
1071
|
+
return { ...process.env, ...env };
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
export function resolveRuntimeEnv(env = {}) {
|
|
1075
|
+
return createRuntimeEnv(env);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
export function createLoggerBackedRuntime(logger = console) {
|
|
1079
|
+
return { logger };
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
export function createSubsystemLogger() {
|
|
1083
|
+
return console;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
export function buildThreadAwareOutboundSessionRoute(route = {}) {
|
|
1087
|
+
return route;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
export function clearAccountEntryFields(entry = {}) {
|
|
1091
|
+
return { ...entry };
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
export function parseOptionalDelimitedEntries(value = "") {
|
|
1095
|
+
return String(value).split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
export function recoverCurrentThreadSessionId() {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
export function stripChannelTargetPrefix(value) {
|
|
1103
|
+
return String(value ?? "").replace(/^channel:/, "");
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
export function stripTargetKindPrefix(value) {
|
|
1107
|
+
return String(value ?? "").replace(/^[^:]+:/, "");
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
export function tryReadSecretFileSync() {
|
|
1111
|
+
return undefined;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
export function fetchWithTimeout(...args) {
|
|
1115
|
+
return fetch(...args);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
export function fetchWithSsrFGuard(...args) {
|
|
1119
|
+
return fetch(...args);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
export async function postJsonRequest() {
|
|
1123
|
+
return {};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
export function assertOkOrThrowHttpError(response) {
|
|
1127
|
+
return response;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
export function assertOkOrThrowProviderError(response) {
|
|
1131
|
+
return response;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
export function createProviderHttpError(message = "provider http error") {
|
|
1135
|
+
return new Error(message);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
export function extractProviderErrorDetail(error) {
|
|
1139
|
+
return error?.message ?? String(error ?? "");
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
export function extractProviderRequestId() {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
export function formatProviderErrorPayload(value) {
|
|
1147
|
+
return String(value ?? "");
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
export function formatProviderHttpErrorMessage(error) {
|
|
1151
|
+
return error?.message ?? String(error ?? "");
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
export async function readResponseTextLimited(response) {
|
|
1155
|
+
return response?.text ? response.text() : "";
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
export function truncateErrorDetail(value) {
|
|
1159
|
+
return String(value ?? "");
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
export function normalizeBaseUrl(value) {
|
|
1163
|
+
return String(value ?? "").replace(/\\/$/, "");
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
export function resolveProviderRequestCapabilities() {
|
|
1167
|
+
return [];
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
export function resolveProviderRequestPolicy() {
|
|
1171
|
+
return {};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
export function buildHostnameAllowlistPolicyFromSuffixAllowlist() {
|
|
1175
|
+
return {};
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export function closeDispatcher() {}
|
|
1179
|
+
|
|
1180
|
+
export function createPinnedDispatcher() {
|
|
1181
|
+
return {};
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
export function isBlockedHostnameOrIp() {
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export function isPrivateNetworkOptInEnabled() {
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
export function isPrivateOrLoopbackHost() {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
export function mergeSsrFPolicies(...policies) {
|
|
1197
|
+
return Object.assign({}, ...policies);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
export function resolvePinnedHostname(hostname) {
|
|
1201
|
+
return hostname;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
export function resolvePinnedHostnameWithPolicy(hostname) {
|
|
1205
|
+
return { hostname, allowed: true };
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
export function ssrfPolicyFromHttpBaseUrlAllowedHostname(hostname) {
|
|
1209
|
+
return { allow: [hostname] };
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
export function formatErrorMessage(error) {
|
|
1213
|
+
return error?.message ?? String(error ?? "");
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
export function extractErrorCode(error) {
|
|
1217
|
+
return error?.code ?? null;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
export function hasProxyEnvConfigured() {
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
export function isNotFoundPathError(error) {
|
|
1225
|
+
return error?.code === "ENOENT";
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
export function isPathInside() {
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
export function normalizeHostname(value) {
|
|
1233
|
+
return String(value ?? "").toLowerCase();
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
export function openFileWithinRoot() {
|
|
1237
|
+
return undefined;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
export function writeFileFromPathWithinRoot() {
|
|
1241
|
+
return undefined;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
export function redactSensitiveText(value) {
|
|
1245
|
+
return String(value ?? "");
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
export function safeEqualSecret(left, right) {
|
|
1249
|
+
return left === right;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
export function wrapExternalContent(value) {
|
|
1253
|
+
return String(value ?? "");
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
export function renderQrTerminal(value) {
|
|
1257
|
+
return String(value ?? "");
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
export function chunkTextForOutbound(value) {
|
|
1261
|
+
return [String(value ?? "")];
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
export function truncateText(value, maxLength = 4000) {
|
|
1265
|
+
return String(value ?? "").slice(0, maxLength);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
export function createMockPluginRuntime(runtime = {}) {
|
|
1269
|
+
return runtime;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
export class SsrFBlockedError extends Error {
|
|
1273
|
+
constructor(message = "SSRF blocked") {
|
|
1274
|
+
super(message);
|
|
1275
|
+
this.name = "SsrFBlockedError";
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
export class SafeOpenError extends Error {
|
|
1280
|
+
constructor(message = "Safe open failed") {
|
|
1281
|
+
super(message);
|
|
1282
|
+
this.name = "SafeOpenError";
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
export const DEFAULT_CONTEXT_TOKENS = 128000;
|
|
1287
|
+
export const XAI_TOOL_SCHEMA_PROFILE = "xai";
|
|
1288
|
+
export const HTML_ENTITY_TOOL_CALL_ARGUMENTS_ENCODING = "html-entities";
|
|
1289
|
+
export const XAI_UNSUPPORTED_SCHEMA_KEYWORDS = new Set();
|
|
1290
|
+
export const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS = new Set();
|
|
1291
|
+
export const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks("openai-compatible");
|
|
1292
|
+
export const ANTHROPIC_BY_MODEL_REPLAY_HOOKS = buildProviderReplayFamilyHooks("anthropic-by-model");
|
|
1293
|
+
export const NATIVE_ANTHROPIC_REPLAY_HOOKS = buildProviderReplayFamilyHooks("native-anthropic");
|
|
1294
|
+
export const PASSTHROUGH_GEMINI_REPLAY_HOOKS = buildProviderReplayFamilyHooks("passthrough-gemini");
|
|
1295
|
+
export const GOOGLE_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("google-thinking");
|
|
1296
|
+
export const KILOCODE_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("kilocode-thinking");
|
|
1297
|
+
export const MINIMAX_FAST_MODE_STREAM_HOOKS = buildProviderStreamFamilyHooks("minimax-fast-mode");
|
|
1298
|
+
export const MOONSHOT_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("moonshot-thinking");
|
|
1299
|
+
export const OPENAI_RESPONSES_STREAM_HOOKS = buildProviderStreamFamilyHooks("openai-responses");
|
|
1300
|
+
export const OPENROUTER_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("openrouter-thinking");
|
|
1301
|
+
export const TOOL_STREAM_DEFAULT_ON_HOOKS = buildProviderStreamFamilyHooks("tool-stream-default");
|
|
50
1302
|
export const pluginSdkMock = true;
|
|
51
1303
|
|
|
52
1304
|
export default {
|
|
53
|
-
|
|
54
|
-
createPlugin,
|
|
55
|
-
defineChannelPluginEntry,
|
|
56
|
-
definePlugin,
|
|
57
|
-
definePluginEntry,
|
|
58
|
-
pluginSdkMock,
|
|
1305
|
+
${mockSdkExportNames.map((name) => ` ${name},`).join("\n")}
|
|
59
1306
|
};
|
|
60
1307
|
`;
|
|
61
1308
|
}
|
|
1309
|
+
|
|
1310
|
+
function mockSdkSubpathSource(exportNames) {
|
|
1311
|
+
return `${exportNames.map((name) => `export { ${name} } from "./index.js";`).join("\n")}
|
|
1312
|
+
export { default } from "./index.js";
|
|
1313
|
+
`;
|
|
1314
|
+
}
|