vite-plugin-aipanel 1.2.0-beta.0

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.
Files changed (98) hide show
  1. package/README.md +72 -0
  2. package/es/client/vue-devtools-bridge.d.ts +58 -0
  3. package/es/client/vue-devtools-bridge.mjs +150 -0
  4. package/es/core/injector.d.ts +2 -0
  5. package/es/core/injector.mjs +15 -0
  6. package/es/core/mcp-chrome.d.ts +56 -0
  7. package/es/core/mcp-chrome.mjs +133 -0
  8. package/es/core/mcp-proxy.d.ts +23 -0
  9. package/es/core/mcp-proxy.mjs +212 -0
  10. package/es/core/mcp-tools.d.ts +15 -0
  11. package/es/core/mcp-tools.mjs +671 -0
  12. package/es/core/provider-loader.d.ts +19 -0
  13. package/es/core/provider-loader.mjs +30 -0
  14. package/es/core/proxy-server.d.ts +12 -0
  15. package/es/core/proxy-server.mjs +148 -0
  16. package/es/core/service.d.ts +39 -0
  17. package/es/core/service.mjs +267 -0
  18. package/es/endpoints/context.d.ts +3 -0
  19. package/es/endpoints/context.mjs +106 -0
  20. package/es/endpoints/index.d.ts +9 -0
  21. package/es/endpoints/index.mjs +30 -0
  22. package/es/endpoints/logs.d.ts +9 -0
  23. package/es/endpoints/logs.mjs +86 -0
  24. package/es/endpoints/mcp.d.ts +6 -0
  25. package/es/endpoints/mcp.mjs +610 -0
  26. package/es/endpoints/sessions.d.ts +3 -0
  27. package/es/endpoints/sessions.mjs +62 -0
  28. package/es/endpoints/sse.d.ts +3 -0
  29. package/es/endpoints/sse.mjs +44 -0
  30. package/es/endpoints/start.d.ts +3 -0
  31. package/es/endpoints/start.mjs +21 -0
  32. package/es/endpoints/types.d.ts +34 -0
  33. package/es/endpoints/types.mjs +0 -0
  34. package/es/endpoints/vue-devtools.d.ts +9 -0
  35. package/es/endpoints/vue-devtools.mjs +107 -0
  36. package/es/endpoints/warmup.d.ts +3 -0
  37. package/es/endpoints/warmup.mjs +42 -0
  38. package/es/endpoints/widget.d.ts +3 -0
  39. package/es/endpoints/widget.mjs +36 -0
  40. package/es/env.d.ts +1 -0
  41. package/es/index.d.ts +22 -0
  42. package/es/index.mjs +342 -0
  43. package/es/utils/format-bridge.d.ts +9 -0
  44. package/es/utils/format-bridge.mjs +80 -0
  45. package/es/utils/paths.d.ts +3 -0
  46. package/es/utils/paths.mjs +45 -0
  47. package/es/utils/system.d.ts +3 -0
  48. package/es/utils/system.mjs +76 -0
  49. package/lib/client/vue-devtools-bridge.cjs +150 -0
  50. package/lib/client/vue-devtools-bridge.d.ts +58 -0
  51. package/lib/client.css +2 -0
  52. package/lib/client.js +6950 -0
  53. package/lib/core/injector.cjs +34 -0
  54. package/lib/core/injector.d.ts +2 -0
  55. package/lib/core/mcp-chrome.cjs +161 -0
  56. package/lib/core/mcp-chrome.d.ts +56 -0
  57. package/lib/core/mcp-proxy.cjs +244 -0
  58. package/lib/core/mcp-proxy.d.ts +23 -0
  59. package/lib/core/mcp-tools.cjs +694 -0
  60. package/lib/core/mcp-tools.d.ts +15 -0
  61. package/lib/core/provider-loader.cjs +53 -0
  62. package/lib/core/provider-loader.d.ts +19 -0
  63. package/lib/core/proxy-server.cjs +181 -0
  64. package/lib/core/proxy-server.d.ts +12 -0
  65. package/lib/core/service.cjs +289 -0
  66. package/lib/core/service.d.ts +39 -0
  67. package/lib/endpoints/context.cjs +129 -0
  68. package/lib/endpoints/context.d.ts +3 -0
  69. package/lib/endpoints/index.cjs +58 -0
  70. package/lib/endpoints/index.d.ts +9 -0
  71. package/lib/endpoints/logs.cjs +107 -0
  72. package/lib/endpoints/logs.d.ts +9 -0
  73. package/lib/endpoints/mcp.cjs +624 -0
  74. package/lib/endpoints/mcp.d.ts +6 -0
  75. package/lib/endpoints/sessions.cjs +85 -0
  76. package/lib/endpoints/sessions.d.ts +3 -0
  77. package/lib/endpoints/sse.cjs +67 -0
  78. package/lib/endpoints/sse.d.ts +3 -0
  79. package/lib/endpoints/start.cjs +44 -0
  80. package/lib/endpoints/start.d.ts +3 -0
  81. package/lib/endpoints/types.cjs +15 -0
  82. package/lib/endpoints/types.d.ts +34 -0
  83. package/lib/endpoints/vue-devtools.cjs +132 -0
  84. package/lib/endpoints/vue-devtools.d.ts +9 -0
  85. package/lib/endpoints/warmup.cjs +65 -0
  86. package/lib/endpoints/warmup.d.ts +3 -0
  87. package/lib/endpoints/widget.cjs +69 -0
  88. package/lib/endpoints/widget.d.ts +3 -0
  89. package/lib/env.d.ts +1 -0
  90. package/lib/index.cjs +361 -0
  91. package/lib/index.d.ts +22 -0
  92. package/lib/utils/format-bridge.cjs +102 -0
  93. package/lib/utils/format-bridge.d.ts +9 -0
  94. package/lib/utils/paths.cjs +80 -0
  95. package/lib/utils/paths.d.ts +3 -0
  96. package/lib/utils/system.cjs +110 -0
  97. package/lib/utils/system.d.ts +3 -0
  98. package/package.json +68 -0
@@ -0,0 +1,62 @@
1
+ import { SESSIONS_API_PATH } from "@aipanel/core";
2
+ import { RequestContext, createLogger } from "@aipanel/core/node";
3
+ const log = createLogger("Endpoints:Sessions");
4
+ function setupSessionsEndpoint(server, ctx) {
5
+ server.middlewares.use(SESSIONS_API_PATH, async (req, res) => {
6
+ const reqCtx = new RequestContext(req.method || "GET", SESSIONS_API_PATH);
7
+ res.setHeader("Content-Type", "application/json");
8
+ res.setHeader("Access-Control-Allow-Origin", "*");
9
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
10
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
11
+ if (req.method === "OPTIONS") {
12
+ res.writeHead(200);
13
+ res.end();
14
+ reqCtx.end(200);
15
+ return;
16
+ }
17
+ try {
18
+ if (req.method === "GET") {
19
+ reqCtx.checkpoint("Fetching sessions");
20
+ const [sessions, capabilities] = await Promise.all([
21
+ ctx.getSessions(),
22
+ ctx.getCapabilities()
23
+ ]);
24
+ res.writeHead(200);
25
+ res.end(JSON.stringify({ sessions, capabilities }));
26
+ reqCtx.end(200);
27
+ } else if (req.method === "POST") {
28
+ reqCtx.checkpoint("Creating session");
29
+ const newSession = await ctx.createSession();
30
+ res.writeHead(200);
31
+ res.end(JSON.stringify(newSession));
32
+ reqCtx.end(200);
33
+ } else if (req.method === "DELETE") {
34
+ const url = new URL(req.url || "", `http://${req.headers.host}`);
35
+ const sessionId = url.searchParams.get("id");
36
+ if (!sessionId) {
37
+ res.writeHead(400);
38
+ res.end(JSON.stringify({ error: "Session ID is required" }));
39
+ reqCtx.end(400);
40
+ return;
41
+ }
42
+ reqCtx.checkpoint(`Deleting session: ${sessionId}`);
43
+ await ctx.deleteSession(sessionId);
44
+ res.writeHead(200);
45
+ res.end(JSON.stringify({ success: true }));
46
+ reqCtx.end(200);
47
+ } else {
48
+ res.writeHead(405);
49
+ res.end(JSON.stringify({ error: "Method not allowed" }));
50
+ reqCtx.end(405);
51
+ }
52
+ } catch (e) {
53
+ log.error("Session API error", { error: e, method: req.method });
54
+ res.writeHead(500);
55
+ res.end(JSON.stringify({ error: String(e) }));
56
+ reqCtx.error(e);
57
+ }
58
+ });
59
+ }
60
+ export {
61
+ setupSessionsEndpoint
62
+ };
@@ -0,0 +1,3 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import type { EndpointContext } from "./types";
3
+ export declare function setupSseEndpoint(server: ViteDevServer, ctx: EndpointContext): void;
@@ -0,0 +1,44 @@
1
+ import { SSE_EVENTS_PATH } from "@aipanel/core";
2
+ import { RequestContext, createLogger } from "@aipanel/core/node";
3
+ const log = createLogger("Endpoints:SSE");
4
+ function setupSseEndpoint(server, ctx) {
5
+ server.middlewares.use(SSE_EVENTS_PATH, async (req, res) => {
6
+ const reqCtx = new RequestContext("GET", SSE_EVENTS_PATH);
7
+ res.writeHead(200, {
8
+ "Content-Type": "text/event-stream",
9
+ "Cache-Control": "no-cache",
10
+ Connection: "keep-alive",
11
+ "Access-Control-Allow-Origin": "*"
12
+ });
13
+ ctx.sseClients.add(res);
14
+ log.debug("SSE client connected", { totalClients: ctx.sseClients.size });
15
+ res.write(`data: ${JSON.stringify({ type: "CONNECTED" })}
16
+
17
+ `);
18
+ const statusPayload = { type: "STATUS_SYNC" };
19
+ if (ctx.isServiceStarted !== void 0) {
20
+ statusPayload.isStarted = ctx.isServiceStarted;
21
+ }
22
+ if (ctx.currentTask) {
23
+ statusPayload.task = ctx.currentTask.task;
24
+ Object.keys(ctx.currentTask).forEach((key) => {
25
+ if (key !== "task") {
26
+ statusPayload[key] = ctx.currentTask[key];
27
+ }
28
+ });
29
+ }
30
+ res.write(`data: ${JSON.stringify(statusPayload)}
31
+
32
+ `);
33
+ req.on("close", () => {
34
+ ctx.sseClients.delete(res);
35
+ log.debug("SSE client disconnected", {
36
+ totalClients: ctx.sseClients.size
37
+ });
38
+ });
39
+ reqCtx.end(200);
40
+ });
41
+ }
42
+ export {
43
+ setupSseEndpoint
44
+ };
@@ -0,0 +1,3 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import type { EndpointContext } from "./types";
3
+ export declare function setupStartEndpoint(server: ViteDevServer, ctx: EndpointContext): void;
@@ -0,0 +1,21 @@
1
+ import { START_API_PATH } from "@aipanel/core";
2
+ import { RequestContext } from "@aipanel/core/node";
3
+ function setupStartEndpoint(server, ctx) {
4
+ server.middlewares.use(START_API_PATH, async (_req, res) => {
5
+ const reqCtx = new RequestContext("GET", START_API_PATH);
6
+ res.setHeader("Content-Type", "application/json");
7
+ res.setHeader("Access-Control-Allow-Origin", "*");
8
+ res.writeHead(200);
9
+ res.end(JSON.stringify({
10
+ success: true,
11
+ proxyPort: ctx.actualProxyPort,
12
+ webPort: ctx.actualWebPort,
13
+ projectRoot: server.config.root,
14
+ serviceInstanceId: ctx.serviceInstanceId
15
+ }));
16
+ reqCtx.end(200);
17
+ });
18
+ }
19
+ export {
20
+ setupStartEndpoint
21
+ };
@@ -0,0 +1,34 @@
1
+ import type { ChatSession, PageContext, ProviderCapabilities, ServiceStartupTask } from "@aipanel/core";
2
+ import type http from "http";
3
+ export interface EndpointContext {
4
+ get webUrl(): string | null;
5
+ get sseClients(): Set<http.ServerResponse>;
6
+ /** 获取当前活跃 Tab 的上下文(扩展模式)或默认上下文(非扩展模式) */
7
+ getPageContext(): PageContext;
8
+ /** 写入某个 Tab 的上下文 */
9
+ setPageContext(tabId: string, ctx: PageContext): void;
10
+ /** 设置当前活跃 Tab ID */
11
+ setActiveTabId(tabId: string): void;
12
+ /** 清除选中元素(活跃 Tab) */
13
+ clearSelectedElements(): void;
14
+ get isServiceStarted(): boolean;
15
+ get currentTask(): {
16
+ task: ServiceStartupTask;
17
+ data?: Record<string, unknown>;
18
+ } | null;
19
+ get actualProxyPort(): number;
20
+ get actualWebPort(): number;
21
+ get serviceInstanceId(): string;
22
+ getSessions: () => Promise<ChatSession[]>;
23
+ createSession: () => Promise<ChatSession>;
24
+ deleteSession: (id: string) => Promise<void>;
25
+ /** 获取当前 Provider 能力描述(客户端自适应行为依据) */
26
+ getCapabilities: () => ProviderCapabilities;
27
+ resolveWidgetPath: () => string;
28
+ resolveWidgetStylePath: () => string;
29
+ retryWarmupChromeMcp: () => Promise<{
30
+ success: boolean;
31
+ errorType?: string;
32
+ errorMessage?: string;
33
+ }>;
34
+ }
File without changes
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Vue DevTools API 端点
3
+ * 接收 aipanel 插件的请求,通过 MCP 代理在浏览器中执行 window.__aipanel_vue
4
+ */
5
+ import type { ViteDevServer } from "vite";
6
+ import type { McpProxy } from "../core/mcp-proxy";
7
+ export declare const VUE_DEVTOOLS_API_PATH = "/__aipanel_vue_devtools__";
8
+ export declare function setupVueDevtoolsEndpoint(server: ViteDevServer, mcp: McpProxy): void;
9
+ export declare function executeAction(action: string, args: Record<string, unknown> | undefined, mcp: McpProxy, projectOrigins: string[]): Promise<unknown>;
@@ -0,0 +1,107 @@
1
+ import { createLogger } from "@aipanel/core/node";
2
+ import { VUE_DEVTOOLS_ACTIONS } from "@aipanel/core";
3
+ import { getProjectOrigins, validatePageId } from "../core/mcp-chrome.mjs";
4
+ const log = createLogger("Endpoints:VueDevtools");
5
+ const VUE_DEVTOOLS_API_PATH = "/__aipanel_vue_devtools__";
6
+ function setupVueDevtoolsEndpoint(server, mcp) {
7
+ server.middlewares.use(VUE_DEVTOOLS_API_PATH, async (req, res) => {
8
+ res.setHeader("Content-Type", "application/json");
9
+ res.setHeader("Access-Control-Allow-Origin", "*");
10
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
11
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
12
+ if (req.method === "OPTIONS") {
13
+ res.writeHead(200);
14
+ res.end();
15
+ return;
16
+ }
17
+ if (req.method !== "POST") {
18
+ res.writeHead(405);
19
+ res.end(JSON.stringify({ error: "Method not allowed" }));
20
+ return;
21
+ }
22
+ try {
23
+ const body = await readBody(req);
24
+ const { action, args } = JSON.parse(body);
25
+ const result = await executeAction(action, args, mcp, getProjectOrigins(server));
26
+ res.writeHead(200);
27
+ res.end(JSON.stringify({ success: true, data: result }));
28
+ } catch (e) {
29
+ log.error("Vue DevTools API error", { error: e.message });
30
+ res.writeHead(500);
31
+ res.end(JSON.stringify({ success: false, error: e.message }));
32
+ }
33
+ });
34
+ }
35
+ async function executeAction(action, args, mcp, projectOrigins) {
36
+ const pageId = args?.pageId;
37
+ if (typeof pageId !== "number") {
38
+ throw new Error("\u7F3A\u5C11 pageId \u53C2\u6570\uFF0C\u8BF7\u5148\u83B7\u53D6\u9875\u9762 ID");
39
+ }
40
+ const validation = await validatePageId(mcp, pageId, projectOrigins);
41
+ if (!validation.valid) {
42
+ log.error("pageId \u65E0\u6548\u6216\u975E\u9879\u76EE\u9875\u9762", {
43
+ pageId,
44
+ projectPageIds: validation.projectPages.map((p) => p.pageId)
45
+ });
46
+ throw new Error(validation.error);
47
+ }
48
+ await mcp.callChromeDevTool("select_page", { pageId, bringToFront: false });
49
+ const callExpr = buildCallExpr(action, args);
50
+ const result = await mcp.callChromeDevTool("evaluate_script", {
51
+ function: callExpr
52
+ });
53
+ const parsed = parseEvalResult(result);
54
+ return parsed;
55
+ }
56
+ function buildCallExpr(action, args) {
57
+ switch (action) {
58
+ case VUE_DEVTOOLS_ACTIONS.GET_COMPONENT_TREE:
59
+ return `async () => { return await window.__aipanel_vue.api.getInspectorTree({ inspectorId: "components", filter: ${JSON.stringify(args?.filter ?? "")} }) }`;
60
+ case VUE_DEVTOOLS_ACTIONS.GET_COMPONENT_STATE:
61
+ return `async () => { return await window.__aipanel_vue.api.getInspectorState({ inspectorId: "components", nodeId: ${JSON.stringify(args?.nodeId)} }) }`;
62
+ case VUE_DEVTOOLS_ACTIONS.GET_COMPONENT_RENDER_CODE:
63
+ return `async () => { return await window.__aipanel_vue.api.getComponentRenderCode(${JSON.stringify(args?.nodeId)}) }`;
64
+ case VUE_DEVTOOLS_ACTIONS.GET_APPS:
65
+ return `async () => { return window.__aipanel_vue.ctx.state.appRecords.map(r => ({ id: r.id, name: r.name })) }`;
66
+ case VUE_DEVTOOLS_ACTIONS.TOGGLE_APP:
67
+ return `async () => { await window.__aipanel_vue.api.toggleApp(${JSON.stringify(args?.appId)}); return "ok" }`;
68
+ case VUE_DEVTOOLS_ACTIONS.GET_ROUTER_INFO:
69
+ return `async () => { const r = window.__aipanel_vue.router.value; return window.__aipanel_vue.safeStringify({ currentRoute: r?.currentRoute?.value ?? null, routes: r?.getRoutes?.() ?? [] }) }`;
70
+ default:
71
+ throw new Error(`Unknown action: ${action}`);
72
+ }
73
+ }
74
+ function parseEvalResult(result) {
75
+ const r = result;
76
+ const text = r?.result?.content?.[0]?.text;
77
+ if (!text) return null;
78
+ let jsonText = text;
79
+ const mdMatch = text.match(/```(?:json)?\s*\n([\s\S]*?)\n\s*```/);
80
+ if (mdMatch) {
81
+ jsonText = mdMatch[1];
82
+ }
83
+ try {
84
+ return JSON.parse(jsonText);
85
+ } catch {
86
+ return text;
87
+ }
88
+ }
89
+ function readBody(req) {
90
+ return new Promise((resolve, reject) => {
91
+ let body = "";
92
+ req.on("data", (chunk) => {
93
+ body += chunk.toString();
94
+ if (body.length > 1024 * 1024) {
95
+ req.destroy();
96
+ reject(new Error("Request body too large"));
97
+ }
98
+ });
99
+ req.on("end", () => resolve(body));
100
+ req.on("error", reject);
101
+ });
102
+ }
103
+ export {
104
+ VUE_DEVTOOLS_API_PATH,
105
+ executeAction,
106
+ setupVueDevtoolsEndpoint
107
+ };
@@ -0,0 +1,3 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import type { EndpointContext } from "./types";
3
+ export declare function setupWarmupEndpoint(server: ViteDevServer, ctx: EndpointContext): void;
@@ -0,0 +1,42 @@
1
+ import { createLogger } from "@aipanel/core/node";
2
+ import { WARMUP_API_PATH } from "@aipanel/core";
3
+ const log = createLogger("Endpoints:Warmup");
4
+ function setupWarmupEndpoint(server, ctx) {
5
+ server.middlewares.use(WARMUP_API_PATH, async (req, res) => {
6
+ if (req.method !== "POST") {
7
+ res.writeHead(405);
8
+ res.end("Method not allowed");
9
+ return;
10
+ }
11
+ try {
12
+ const result = await ctx.retryWarmupChromeMcp();
13
+ res.setHeader("Content-Type", "application/json");
14
+ res.writeHead(200);
15
+ if (result.success) {
16
+ res.end(JSON.stringify({ success: true }));
17
+ } else {
18
+ res.end(
19
+ JSON.stringify({
20
+ success: false,
21
+ errorType: result.errorType,
22
+ error: result.errorMessage
23
+ })
24
+ );
25
+ }
26
+ } catch (e) {
27
+ log.error("Failed to retry warmup", { error: e });
28
+ res.setHeader("Content-Type", "application/json");
29
+ res.writeHead(500);
30
+ res.end(
31
+ JSON.stringify({
32
+ success: false,
33
+ errorType: "UNKNOWN",
34
+ error: String(e)
35
+ })
36
+ );
37
+ }
38
+ });
39
+ }
40
+ export {
41
+ setupWarmupEndpoint
42
+ };
@@ -0,0 +1,3 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import type { EndpointContext } from "./types";
3
+ export declare function setupWidgetEndpoints(server: ViteDevServer, ctx: EndpointContext): void;
@@ -0,0 +1,36 @@
1
+ import fs from "fs";
2
+ import { WIDGET_SCRIPT_PATH, WIDGET_STYLE_PATH } from "@aipanel/core";
3
+ import { RequestContext } from "@aipanel/core/node";
4
+ function setupWidgetEndpoints(server, ctx) {
5
+ server.middlewares.use(WIDGET_SCRIPT_PATH, async (_req, res) => {
6
+ const reqCtx = new RequestContext("GET", WIDGET_SCRIPT_PATH);
7
+ const widgetPath = ctx.resolveWidgetPath();
8
+ if (fs.existsSync(widgetPath)) {
9
+ res.setHeader("Content-Type", "application/javascript");
10
+ res.setHeader("Access-Control-Allow-Origin", "*");
11
+ fs.createReadStream(widgetPath).pipe(res);
12
+ reqCtx.end(200);
13
+ } else {
14
+ res.writeHead(404);
15
+ res.end("Widget script not found");
16
+ reqCtx.end(404);
17
+ }
18
+ });
19
+ server.middlewares.use(WIDGET_STYLE_PATH, async (_req, res) => {
20
+ const reqCtx = new RequestContext("GET", WIDGET_STYLE_PATH);
21
+ const stylePath = ctx.resolveWidgetStylePath();
22
+ if (fs.existsSync(stylePath)) {
23
+ res.setHeader("Content-Type", "text/css");
24
+ res.setHeader("Access-Control-Allow-Origin", "*");
25
+ fs.createReadStream(stylePath).pipe(res);
26
+ reqCtx.end(200);
27
+ } else {
28
+ res.writeHead(404);
29
+ res.end("Widget style not found");
30
+ reqCtx.end(404);
31
+ }
32
+ });
33
+ }
34
+ export {
35
+ setupWidgetEndpoints
36
+ };
package/es/env.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference types="chrome" />
package/es/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { Plugin } from "vite";
2
+ import type { PluginOptions } from "@aipanel/core";
3
+ import { type ProviderId } from "./core/provider-loader";
4
+ import type { OpenCodeProviderOptions } from "@aipanel/provider-opencode";
5
+ import type { DeepSeekProviderOptions } from "@aipanel/provider-deepseek";
6
+ export type { PluginOptions } from "@aipanel/core";
7
+ export type { OpenCodeProviderOptions } from "@aipanel/provider-opencode";
8
+ export type { DeepSeekProviderOptions } from "@aipanel/provider-deepseek";
9
+ export type { ProviderId };
10
+ /**
11
+ * 编译期映射:provider id → providerOptions schema
12
+ * 与 provider-loader 的运行时包名映射对应;未知 provider 回退为宽松对象。
13
+ */
14
+ type ProviderOptionsSchema<ID extends ProviderId> = ID extends "default" | "opencode" ? OpenCodeProviderOptions : ID extends "deepseek" ? DeepSeekProviderOptions : Record<string, unknown>;
15
+ /**
16
+ * AIPanel Vite 插件
17
+ * provider 字段收窄为已登记的 ProviderId 联合类型(拼写错误在编译期报错);
18
+ * providerOptions 的类型根据 options.provider 自动推断(未配置时默认 "default" → opencode)。
19
+ */
20
+ export default function aipanelPlugin<const P extends ProviderId = "default">(options?: Omit<PluginOptions<ProviderOptionsSchema<P>>, "provider"> & {
21
+ provider?: P;
22
+ }): Plugin[];