diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 863e96d8839..9c2ffe0e87b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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] diff --git a/package.json b/package.json index 7afea94ef65..6c536f0a518 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/check-extension-plugin-sdk-boundary.mjs b/scripts/check-extension-plugin-sdk-boundary.mjs new file mode 100644 index 00000000000..90933218501 --- /dev/null +++ b/scripts/check-extension-plugin-sdk-boundary.mjs @@ -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(); +} diff --git a/test/extension-plugin-sdk-boundary.test.ts b/test/extension-plugin-sdk-boundary.test.ts new file mode 100644 index 00000000000..90372348a95 --- /dev/null +++ b/test/extension-plugin-sdk-boundary.test.ts @@ -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"), + ); + }); +}); diff --git a/test/fixtures/extension-plugin-sdk-internal-inventory.json b/test/fixtures/extension-plugin-sdk-internal-inventory.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/test/fixtures/extension-plugin-sdk-internal-inventory.json @@ -0,0 +1 @@ +[] diff --git a/test/fixtures/extension-src-outside-plugin-sdk-inventory.json b/test/fixtures/extension-src-outside-plugin-sdk-inventory.json new file mode 100644 index 00000000000..4cd9a910b0d --- /dev/null +++ b/test/fixtures/extension-src-outside-plugin-sdk-inventory.json @@ -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" + } +]