test add extension plugin sdk boundary guards

This commit is contained in:
Tak Hoffman
2026-03-17 23:35:39 -05:00
committed by Val Alexander
parent a03f43d5bd
commit 5484225b2d
6 changed files with 830 additions and 0 deletions

View File

@@ -398,6 +398,100 @@ jobs:
echo "::error::Web search provider boundary grace period ended at ${WEB_SEARCH_PROVIDER_BOUNDARY_ENFORCE_AFTER}. ${fix_instructions}"
exit "$status"
extension-src-outside-plugin-sdk-boundary:
name: "extension-src-outside-plugin-sdk-boundary"
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
env:
EXTENSION_PLUGIN_SDK_BOUNDARY_ENFORCE_AFTER: "2026-03-24T05:00:00Z"
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Run extension src boundary guard with grace period
shell: bash
run: |
set -euo pipefail
tmp_output="$(mktemp)"
if pnpm run lint:extensions:no-src-outside-plugin-sdk >"$tmp_output" 2>&1; then
cat "$tmp_output"
rm -f "$tmp_output"
exit 0
fi
status=$?
cat "$tmp_output"
rm -f "$tmp_output"
now_epoch="$(date -u +%s)"
enforce_epoch="$(date -u -d "$EXTENSION_PLUGIN_SDK_BOUNDARY_ENFORCE_AFTER" +%s)"
fix_instructions="If you are an LLM agent fixing this: run 'pnpm run lint:extensions:no-src-outside-plugin-sdk', move extension imports off core src paths and onto src/plugin-sdk/**, and if the remaining inventory is intentional for now update test/fixtures/extension-src-outside-plugin-sdk-inventory.json in the same PR."
if [ "$now_epoch" -lt "$enforce_epoch" ]; then
echo "::warning::Extension src boundary violations are temporarily allowed until ${EXTENSION_PLUGIN_SDK_BOUNDARY_ENFORCE_AFTER}. This grace period ends in one week from the rollout date. After that timestamp this job will fail unless the inventory is reduced or the baseline is intentionally updated. ${fix_instructions}"
exit 0
fi
echo "::error::Extension src boundary grace period ended at ${EXTENSION_PLUGIN_SDK_BOUNDARY_ENFORCE_AFTER}. ${fix_instructions}"
exit "$status"
extension-plugin-sdk-internal-boundary:
name: "extension-plugin-sdk-internal-boundary"
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
env:
EXTENSION_PLUGIN_SDK_INTERNAL_ENFORCE_AFTER: "2026-03-24T05:00:00Z"
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Run extension plugin-sdk-internal guard with grace period
shell: bash
run: |
set -euo pipefail
tmp_output="$(mktemp)"
if pnpm run lint:extensions:no-plugin-sdk-internal >"$tmp_output" 2>&1; then
cat "$tmp_output"
rm -f "$tmp_output"
exit 0
fi
status=$?
cat "$tmp_output"
rm -f "$tmp_output"
now_epoch="$(date -u +%s)"
enforce_epoch="$(date -u -d "$EXTENSION_PLUGIN_SDK_INTERNAL_ENFORCE_AFTER" +%s)"
fix_instructions="If you are an LLM agent fixing this: run 'pnpm run lint:extensions:no-plugin-sdk-internal', remove extension imports of src/plugin-sdk-internal/** in favor of src/plugin-sdk/**, and if the remaining inventory is intentional for now update test/fixtures/extension-plugin-sdk-internal-inventory.json in the same PR."
if [ "$now_epoch" -lt "$enforce_epoch" ]; then
echo "::warning::Extension plugin-sdk-internal boundary violations are temporarily allowed until ${EXTENSION_PLUGIN_SDK_INTERNAL_ENFORCE_AFTER}. This grace period ends in one week from the rollout date. After that timestamp this job will fail unless the inventory is reduced or the baseline is intentionally updated. ${fix_instructions}"
exit 0
fi
echo "::error::Extension plugin-sdk-internal boundary grace period ended at ${EXTENSION_PLUGIN_SDK_INTERNAL_ENFORCE_AFTER}. ${fix_instructions}"
exit "$status"
build-smoke:
name: "build-smoke"
needs: [docs-scope, changed-scope]

View File

@@ -546,6 +546,8 @@
"lint:auth:pairing-account-scope": "node scripts/check-pairing-account-scope.mjs",
"lint:docs": "pnpm dlx markdownlint-cli2",
"lint:docs:fix": "pnpm dlx markdownlint-cli2 --fix",
"lint:extensions:no-plugin-sdk-internal": "node scripts/check-extension-plugin-sdk-boundary.mjs --mode=plugin-sdk-internal",
"lint:extensions:no-src-outside-plugin-sdk": "node scripts/check-extension-plugin-sdk-boundary.mjs --mode=src-outside-plugin-sdk",
"lint:fix": "oxlint --type-aware --fix && pnpm format",
"lint:plugins:no-extension-imports": "node scripts/check-plugin-extension-import-boundary.mjs",
"lint:plugins:no-extension-src-imports": "node --import tsx scripts/check-no-extension-src-imports.ts",

View File

@@ -0,0 +1,267 @@
#!/usr/bin/env node
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import ts from "typescript";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const extensionsRoot = path.join(repoRoot, "extensions");
const MODES = new Set(["src-outside-plugin-sdk", "plugin-sdk-internal"]);
const baselinePathByMode = {
"src-outside-plugin-sdk": path.join(
repoRoot,
"test",
"fixtures",
"extension-src-outside-plugin-sdk-inventory.json",
),
"plugin-sdk-internal": path.join(
repoRoot,
"test",
"fixtures",
"extension-plugin-sdk-internal-inventory.json",
),
};
const ruleTextByMode = {
"src-outside-plugin-sdk":
"Rule: production extensions/** must not import src/** outside src/plugin-sdk/**",
"plugin-sdk-internal":
"Rule: production extensions/** must not import src/plugin-sdk-internal/**",
};
function normalizePath(filePath) {
return path.relative(repoRoot, filePath).split(path.sep).join("/");
}
function isCodeFile(fileName) {
return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(fileName);
}
function isTestLikeFile(relativePath) {
return (
/(^|\/)(__tests__|fixtures)\//.test(relativePath) ||
/\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(relativePath)
);
}
async function collectExtensionSourceFiles(rootDir) {
const out = [];
async function walk(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.name === "dist" || entry.name === "node_modules") {
continue;
}
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
continue;
}
if (!entry.isFile() || !isCodeFile(entry.name)) {
continue;
}
const relativePath = normalizePath(fullPath);
if (isTestLikeFile(relativePath)) {
continue;
}
out.push(fullPath);
}
}
await walk(rootDir);
return out.toSorted((left, right) => normalizePath(left).localeCompare(normalizePath(right)));
}
function toLine(sourceFile, node) {
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
}
function resolveSpecifier(specifier, importerFile) {
if (specifier.startsWith(".")) {
return normalizePath(path.resolve(path.dirname(importerFile), specifier));
}
if (specifier.startsWith("/")) {
return normalizePath(specifier);
}
return null;
}
function classifyReason(mode, kind, resolvedPath) {
const verb =
kind === "export"
? "re-exports"
: kind === "dynamic-import"
? "dynamically imports"
: "imports";
if (mode === "plugin-sdk-internal") {
return `${verb} src/plugin-sdk-internal from an extension`;
}
if (resolvedPath.startsWith("src/plugin-sdk/")) {
return `${verb} allowed plugin-sdk path`;
}
return `${verb} core src path outside plugin-sdk from an extension`;
}
function compareEntries(left, right) {
return (
left.file.localeCompare(right.file) ||
left.line - right.line ||
left.kind.localeCompare(right.kind) ||
left.specifier.localeCompare(right.specifier) ||
left.resolvedPath.localeCompare(right.resolvedPath) ||
left.reason.localeCompare(right.reason)
);
}
function shouldReport(mode, resolvedPath) {
if (!resolvedPath?.startsWith("src/")) {
return false;
}
if (mode === "plugin-sdk-internal") {
return resolvedPath.startsWith("src/plugin-sdk-internal/");
}
return !resolvedPath.startsWith("src/plugin-sdk/");
}
function collectFromSourceFile(mode, sourceFile, filePath) {
const entries = [];
function push(kind, specifierNode, specifier) {
const resolvedPath = resolveSpecifier(specifier, filePath);
if (!shouldReport(mode, resolvedPath)) {
return;
}
entries.push({
file: normalizePath(filePath),
line: toLine(sourceFile, specifierNode),
kind,
specifier,
resolvedPath,
reason: classifyReason(mode, kind, resolvedPath),
});
}
function visit(node) {
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
push("import", node.moduleSpecifier, node.moduleSpecifier.text);
} else if (
ts.isExportDeclaration(node) &&
node.moduleSpecifier &&
ts.isStringLiteral(node.moduleSpecifier)
) {
push("export", node.moduleSpecifier, node.moduleSpecifier.text);
} else if (
ts.isCallExpression(node) &&
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
node.arguments.length === 1 &&
ts.isStringLiteral(node.arguments[0])
) {
push("dynamic-import", node.arguments[0], node.arguments[0].text);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return entries;
}
export async function collectExtensionPluginSdkBoundaryInventory(mode) {
if (!MODES.has(mode)) {
throw new Error(`Unknown mode: ${mode}`);
}
const files = await collectExtensionSourceFiles(extensionsRoot);
const inventory = [];
for (const filePath of files) {
const source = await fs.readFile(filePath, "utf8");
const scriptKind =
filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
const sourceFile = ts.createSourceFile(
filePath,
source,
ts.ScriptTarget.Latest,
true,
scriptKind,
);
inventory.push(...collectFromSourceFile(mode, sourceFile, filePath));
}
return inventory.toSorted(compareEntries);
}
export async function readExpectedInventory(mode) {
return JSON.parse(await fs.readFile(baselinePathByMode[mode], "utf8"));
}
export function diffInventory(expected, actual) {
const expectedKeys = new Set(expected.map((entry) => JSON.stringify(entry)));
const actualKeys = new Set(actual.map((entry) => JSON.stringify(entry)));
return {
missing: expected
.filter((entry) => !actualKeys.has(JSON.stringify(entry)))
.toSorted(compareEntries),
unexpected: actual
.filter((entry) => !expectedKeys.has(JSON.stringify(entry)))
.toSorted(compareEntries),
};
}
function formatInventoryHuman(mode, inventory) {
const lines = [ruleTextByMode[mode]];
if (inventory.length === 0) {
lines.push("No extension plugin-sdk boundary violations found.");
return lines.join("\n");
}
lines.push("Extension boundary inventory:");
let activeFile = "";
for (const entry of inventory) {
if (entry.file !== activeFile) {
activeFile = entry.file;
lines.push(activeFile);
}
lines.push(` - line ${entry.line} [${entry.kind}] ${entry.reason}`);
lines.push(` specifier: ${entry.specifier}`);
lines.push(` resolved: ${entry.resolvedPath}`);
}
return lines.join("\n");
}
export async function main(argv = process.argv.slice(2)) {
const json = argv.includes("--json");
const modeArg = argv.find((arg) => arg.startsWith("--mode="));
const mode = modeArg?.slice("--mode=".length) ?? "src-outside-plugin-sdk";
if (!MODES.has(mode)) {
throw new Error(`Unknown mode: ${mode}`);
}
const actual = await collectExtensionPluginSdkBoundaryInventory(mode);
if (json) {
process.stdout.write(`${JSON.stringify(actual, null, 2)}\n`);
return;
}
const expected = await readExpectedInventory(mode);
const diff = diffInventory(expected, actual);
console.log(formatInventoryHuman(mode, actual));
if (diff.missing.length === 0 && diff.unexpected.length === 0) {
console.log(`Baseline matches (${actual.length} entries).`);
return;
}
if (diff.missing.length > 0) {
console.error(`Missing baseline entries (${diff.missing.length}):`);
for (const entry of diff.missing) {
console.error(` - ${entry.file}:${entry.line} ${entry.reason}`);
}
}
if (diff.unexpected.length > 0) {
console.error(`Unexpected inventory entries (${diff.unexpected.length}):`);
for (const entry of diff.unexpected) {
console.error(` - ${entry.file}:${entry.line} ${entry.reason}`);
}
}
process.exitCode = 1;
}
if (path.resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) {
await main();
}

View File

@@ -0,0 +1,104 @@
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
collectExtensionPluginSdkBoundaryInventory,
diffInventory,
} from "../scripts/check-extension-plugin-sdk-boundary.mjs";
const repoRoot = process.cwd();
const scriptPath = path.join(repoRoot, "scripts", "check-extension-plugin-sdk-boundary.mjs");
function readBaseline(fileName: string) {
return JSON.parse(readFileSync(path.join(repoRoot, "test", "fixtures", fileName), "utf8"));
}
describe("extension src outside plugin-sdk boundary inventory", () => {
it("produces stable sorted output", async () => {
const first = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
const second = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
expect(second).toEqual(first);
expect(
[...first].toSorted(
(left, right) =>
left.file.localeCompare(right.file) ||
left.line - right.line ||
left.kind.localeCompare(right.kind) ||
left.specifier.localeCompare(right.specifier) ||
left.resolvedPath.localeCompare(right.resolvedPath) ||
left.reason.localeCompare(right.reason),
),
).toEqual(first);
});
it("captures known current production violations", async () => {
const inventory = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
expect(inventory).toContainEqual(
expect.objectContaining({
file: "extensions/brave/src/brave-web-search-provider.ts",
resolvedPath: "src/agents/tools/common.js",
}),
);
expect(inventory).toContainEqual(
expect.objectContaining({
file: "extensions/discord/src/runtime-api.ts",
resolvedPath: "src/config/types.secrets.js",
}),
);
});
it("matches the checked-in baseline", async () => {
const expected = readBaseline("extension-src-outside-plugin-sdk-inventory.json");
const actual = await collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk");
expect(diffInventory(expected, actual)).toEqual({ missing: [], unexpected: [] });
});
it("script json output matches the baseline exactly", () => {
const stdout = execFileSync(
process.execPath,
[scriptPath, "--mode=src-outside-plugin-sdk", "--json"],
{
cwd: repoRoot,
encoding: "utf8",
},
);
expect(JSON.parse(stdout)).toEqual(
readBaseline("extension-src-outside-plugin-sdk-inventory.json"),
);
});
});
describe("extension plugin-sdk-internal boundary inventory", () => {
it("is currently empty", async () => {
const inventory = await collectExtensionPluginSdkBoundaryInventory("plugin-sdk-internal");
expect(inventory).toEqual([]);
});
it("matches the checked-in empty baseline", async () => {
const expected = readBaseline("extension-plugin-sdk-internal-inventory.json");
const actual = await collectExtensionPluginSdkBoundaryInventory("plugin-sdk-internal");
expect(diffInventory(expected, actual)).toEqual({ missing: [], unexpected: [] });
});
it("script json output matches the empty baseline exactly", () => {
const stdout = execFileSync(
process.execPath,
[scriptPath, "--mode=plugin-sdk-internal", "--json"],
{
cwd: repoRoot,
encoding: "utf8",
},
);
expect(JSON.parse(stdout)).toEqual(
readBaseline("extension-plugin-sdk-internal-inventory.json"),
);
});
});

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,362 @@
[
{
"file": "extensions/brave/src/brave-web-search-provider.ts",
"line": 2,
"kind": "import",
"specifier": "../../../src/agents/tools/common.js",
"resolvedPath": "src/agents/tools/common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/brave/src/brave-web-search-provider.ts",
"line": 3,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/brave/src/brave-web-search-provider.ts",
"line": 19,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/brave/src/brave-web-search-provider.ts",
"line": 23,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-config.js",
"resolvedPath": "src/agents/tools/web-search-provider-config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/brave/src/brave-web-search-provider.ts",
"line": 24,
"kind": "import",
"specifier": "../../../src/cli/command-format.js",
"resolvedPath": "src/cli/command-format.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/brave/src/brave-web-search-provider.ts",
"line": 25,
"kind": "import",
"specifier": "../../../src/config/config.js",
"resolvedPath": "src/config/config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/brave/src/brave-web-search-provider.ts",
"line": 29,
"kind": "import",
"specifier": "../../../src/plugins/types.js",
"resolvedPath": "src/plugins/types.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/brave/src/brave-web-search-provider.ts",
"line": 30,
"kind": "import",
"specifier": "../../../src/security/external-content.js",
"resolvedPath": "src/security/external-content.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/discord/src/runtime-api.ts",
"line": 38,
"kind": "export",
"specifier": "../../../src/agents/date-time.js",
"resolvedPath": "src/agents/date-time.js",
"reason": "re-exports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/discord/src/runtime-api.ts",
"line": 39,
"kind": "export",
"specifier": "../../../src/agents/sandbox-paths.js",
"resolvedPath": "src/agents/sandbox-paths.js",
"reason": "re-exports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/discord/src/runtime-api.ts",
"line": 41,
"kind": "export",
"specifier": "../../../src/polls.js",
"resolvedPath": "src/polls.js",
"reason": "re-exports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/discord/src/runtime-api.ts",
"line": 42,
"kind": "export",
"specifier": "../../../src/config/types.js",
"resolvedPath": "src/config/types.js",
"reason": "re-exports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/discord/src/runtime-api.ts",
"line": 47,
"kind": "export",
"specifier": "../../../src/config/types.secrets.js",
"resolvedPath": "src/config/types.secrets.js",
"reason": "re-exports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/firecrawl/src/firecrawl-search-provider.ts",
"line": 5,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-config.js",
"resolvedPath": "src/agents/tools/web-search-provider-config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/firecrawl/src/firecrawl-search-provider.ts",
"line": 6,
"kind": "import",
"specifier": "../../../src/plugins/enable.js",
"resolvedPath": "src/plugins/enable.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/firecrawl/src/firecrawl-search-provider.ts",
"line": 7,
"kind": "import",
"specifier": "../../../src/plugins/types.js",
"resolvedPath": "src/plugins/types.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/google/src/gemini-web-search-provider.ts",
"line": 2,
"kind": "import",
"specifier": "../../../src/agents/tools/common.js",
"resolvedPath": "src/agents/tools/common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/google/src/gemini-web-search-provider.ts",
"line": 3,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-citation-redirect.js",
"resolvedPath": "src/agents/tools/web-search-citation-redirect.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/google/src/gemini-web-search-provider.ts",
"line": 4,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/google/src/gemini-web-search-provider.ts",
"line": 17,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/google/src/gemini-web-search-provider.ts",
"line": 21,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-config.js",
"resolvedPath": "src/agents/tools/web-search-provider-config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/google/src/gemini-web-search-provider.ts",
"line": 22,
"kind": "import",
"specifier": "../../../src/config/config.js",
"resolvedPath": "src/config/config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/google/src/gemini-web-search-provider.ts",
"line": 26,
"kind": "import",
"specifier": "../../../src/plugins/types.js",
"resolvedPath": "src/plugins/types.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/google/src/gemini-web-search-provider.ts",
"line": 27,
"kind": "import",
"specifier": "../../../src/security/external-content.js",
"resolvedPath": "src/security/external-content.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/moonshot/src/kimi-web-search-provider.ts",
"line": 2,
"kind": "import",
"specifier": "../../../src/agents/tools/common.js",
"resolvedPath": "src/agents/tools/common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/moonshot/src/kimi-web-search-provider.ts",
"line": 3,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/moonshot/src/kimi-web-search-provider.ts",
"line": 16,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/moonshot/src/kimi-web-search-provider.ts",
"line": 20,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-config.js",
"resolvedPath": "src/agents/tools/web-search-provider-config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/moonshot/src/kimi-web-search-provider.ts",
"line": 21,
"kind": "import",
"specifier": "../../../src/config/config.js",
"resolvedPath": "src/config/config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/moonshot/src/kimi-web-search-provider.ts",
"line": 25,
"kind": "import",
"specifier": "../../../src/plugins/types.js",
"resolvedPath": "src/plugins/types.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/moonshot/src/kimi-web-search-provider.ts",
"line": 26,
"kind": "import",
"specifier": "../../../src/security/external-content.js",
"resolvedPath": "src/security/external-content.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/perplexity/src/perplexity-web-search-provider.ts",
"line": 6,
"kind": "import",
"specifier": "../../../src/agents/tools/common.js",
"resolvedPath": "src/agents/tools/common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/perplexity/src/perplexity-web-search-provider.ts",
"line": 7,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/perplexity/src/perplexity-web-search-provider.ts",
"line": 25,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/perplexity/src/perplexity-web-search-provider.ts",
"line": 29,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-config.js",
"resolvedPath": "src/agents/tools/web-search-provider-config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/perplexity/src/perplexity-web-search-provider.ts",
"line": 30,
"kind": "import",
"specifier": "../../../src/config/config.js",
"resolvedPath": "src/config/config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/perplexity/src/perplexity-web-search-provider.ts",
"line": 35,
"kind": "import",
"specifier": "../../../src/plugins/types.js",
"resolvedPath": "src/plugins/types.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/perplexity/src/perplexity-web-search-provider.ts",
"line": 36,
"kind": "import",
"specifier": "../../../src/security/external-content.js",
"resolvedPath": "src/security/external-content.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/xai/src/grok-web-search-provider.ts",
"line": 2,
"kind": "import",
"specifier": "../../../src/agents/tools/common.js",
"resolvedPath": "src/agents/tools/common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/xai/src/grok-web-search-provider.ts",
"line": 3,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/xai/src/grok-web-search-provider.ts",
"line": 16,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-common.js",
"resolvedPath": "src/agents/tools/web-search-provider-common.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/xai/src/grok-web-search-provider.ts",
"line": 20,
"kind": "import",
"specifier": "../../../src/agents/tools/web-search-provider-config.js",
"resolvedPath": "src/agents/tools/web-search-provider-config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/xai/src/grok-web-search-provider.ts",
"line": 21,
"kind": "import",
"specifier": "../../../src/config/config.js",
"resolvedPath": "src/config/config.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/xai/src/grok-web-search-provider.ts",
"line": 25,
"kind": "import",
"specifier": "../../../src/plugins/types.js",
"resolvedPath": "src/plugins/types.js",
"reason": "imports core src path outside plugin-sdk from an extension"
},
{
"file": "extensions/xai/src/grok-web-search-provider.ts",
"line": 26,
"kind": "import",
"specifier": "../../../src/security/external-content.js",
"resolvedPath": "src/security/external-content.js",
"reason": "imports core src path outside plugin-sdk from an extension"
}
]