@ai_mind/ade-plugin-sdk 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +67 -2
- package/cli/build.mjs +154 -0
- package/index.d.ts +77 -0
- package/index.mjs +9 -0
- package/package.json +21 -4
- package/types/adapter.d.ts +153 -0
- package/types/ade-types.d.ts +712 -0
- package/types/api.d.ts +207 -0
- package/types/plugins.d.ts +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sepmini
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ADE 插件 SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
公开 npm 包 **`@ai_mind/ade-plugin-sdk`**,提供 ADE 插件 API 的自包含 TypeScript 类型、helper 和 `ade-plugin-build`。SDK 当前版本为 **0.1.0**,要求 **ADE 0.45.0 或更新的 0.x 版本**,对应插件 `apiVersion: 1`。
|
|
4
|
+
|
|
5
|
+
SDK 与 ADE 的 **major 必须一致**,minor 和 patch 分别发布:SDK `0.x` 面向 ADE `0.x`,SDK `1.x` 面向 ADE `1.x`。major 匹配后,仍需满足 SDK 要求的最低 ADE 版本与插件 `apiVersion`,并确认当前宿主已提供所用功能。插件自身的 `version` 独立管理,下面的 `1.0.0` 是示例插件的版本。
|
|
6
|
+
|
|
7
|
+
开发插件无需获取 ADE 源码。安装 Node.js / npm 后,在自己的插件目录中运行:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm init -y
|
|
11
|
+
npm install --save-dev @ai_mind/ade-plugin-sdk@0 @types/react @types/react-dom
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`@0` 选择与 ADE `0.x` 对应的 SDK major;使用 ADE `1.x` 时改为 `@1`,并满足对应 SDK 的最低版本要求。
|
|
15
|
+
|
|
16
|
+
保存 `plugin.json`:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"id": "my-plugin",
|
|
21
|
+
"name": "我的插件",
|
|
22
|
+
"version": "1.0.0",
|
|
23
|
+
"apiVersion": 1,
|
|
24
|
+
"adeMajor": 0,
|
|
25
|
+
"renderer": "lib/renderer.js",
|
|
26
|
+
"rendererFormat": "register"
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
保存 `src/renderer.tsx`:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { useState } from 'react'
|
|
34
|
+
import { definePlugin } from '@ai_mind/ade-plugin-sdk'
|
|
35
|
+
import type { RendererPluginContext } from '@ai_mind/ade-plugin-sdk'
|
|
36
|
+
|
|
37
|
+
export default definePlugin({
|
|
38
|
+
activate(ctx: RendererPluginContext) {
|
|
39
|
+
function Counter() {
|
|
40
|
+
const [count, setCount] = useState(0)
|
|
41
|
+
return <button onClick={() => setCount(count + 1)}>点击计数:{count}</button>
|
|
42
|
+
}
|
|
43
|
+
ctx.registerView({ id: 'counter', title: '我的插件', component: Counter })
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
构建后,在 ADE 的「设置 → 插件」中选择「本地目录」,安装自己的插件目录:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
npx ade-plugin-build .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
builder 根据 SDK 自身的 major 自动将 `adeMajor` 写入 `plugin.json`。手写 JavaScript 插件也应声明该字段;ADE 拒绝加载跨 major 插件。旧插件未声明 `adeMajor` 时,继续按 `apiVersion` 判断兼容。
|
|
55
|
+
|
|
56
|
+
React 与 ReactDOM 由 ADE 提供,构建时不会打进插件。构建只发生在插件开发阶段;用户安装的插件必须包含 `plugin.json` 指向的 JavaScript 文件,ADE 不会自动编译 TypeScript 或执行安装脚本。
|
|
57
|
+
|
|
58
|
+
本地目录安装会复制文件。修改代码并重新构建后,在 ADE 重新安装同一个目录;相同插件 ID 会更新副本并保留配置。
|
|
59
|
+
|
|
60
|
+
完整教程已内置在 ADE 的「设置 → 插件 → 开发指南」,支持离线阅读和复制代码。无需 SDK 的 JavaScript 示例和 main、设置、slots、命令、服务及清理规则都在指南中;内置「工作区工具」可在插件管理中启用体验。仓库维护的同一份教程位于 `docs/plugins.md`。
|
|
61
|
+
|
|
62
|
+
需要离线开发包时,点击指南顶部「导出 SDK」,用实际文件路径安装:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npm install --save-dev /绝对路径/ai_mind-ade-plugin-sdk-0.1.0.tgz @types/react @types/react-dom
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`.tgz` 包含 SDK 本身;首次安装仍需依赖缓存或网络来取得构建器和 React 类型。宿主保留旧离线包的 `@ade/plugin-sdk` 和开发阶段的 `@ai_mind/plugin-sdk` 模块别名,兼容已有包与预构建插件;新项目从 npm 安装正式包 `@ai_mind/ade-plugin-sdk`。
|
package/cli/build.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { build } from 'esbuild'
|
|
3
|
+
import fs from 'node:fs/promises'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const modulePath = await fs.realpath(fileURLToPath(import.meta.url))
|
|
8
|
+
const sdkPackage = JSON.parse(await fs.readFile(path.resolve(path.dirname(modulePath), '../package.json'), 'utf8'))
|
|
9
|
+
const sdkVersion = /^(\d+)\.\d+\.\d+(?:[-+][\w.-]+)?$/.exec(sdkPackage.version)
|
|
10
|
+
const sdkMajor = sdkVersion ? Number(sdkVersion[1]) : NaN
|
|
11
|
+
if (!Number.isSafeInteger(sdkMajor)) throw new Error('插件 SDK 版本无效')
|
|
12
|
+
|
|
13
|
+
const sharedModules = ['react', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'react-dom', 'react-dom/client', '@ai_mind/ade-plugin-sdk', '@ai_mind/plugin-sdk', '@ade/plugin-sdk']
|
|
14
|
+
const assetLoaders = Object.fromEntries([
|
|
15
|
+
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif', '.ico',
|
|
16
|
+
'.woff', '.woff2', '.ttf', '.otf', '.eot'
|
|
17
|
+
].map((extension) => [extension, 'file']))
|
|
18
|
+
|
|
19
|
+
function ownedPath(directory, relative, label) {
|
|
20
|
+
if (typeof relative !== 'string' || !relative || path.isAbsolute(relative)) {
|
|
21
|
+
throw new Error(`${label}必须是插件目录内的相对路径`)
|
|
22
|
+
}
|
|
23
|
+
const resolved = path.resolve(directory, relative)
|
|
24
|
+
if (!resolved.startsWith(`${directory}${path.sep}`)) throw new Error(`${label}不能越过插件目录`)
|
|
25
|
+
return resolved
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 将 TSX 和依赖预构建为浏览器插件,保留 ADE 提供的 React 模块身份。 */
|
|
29
|
+
export async function buildPlugin(directory, options = {}) {
|
|
30
|
+
directory = path.resolve(directory)
|
|
31
|
+
const manifest = JSON.parse(await fs.readFile(path.join(directory, 'plugin.json'), 'utf8'))
|
|
32
|
+
const pluginId = manifest.id ?? manifest.name
|
|
33
|
+
if (typeof pluginId !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(pluginId)) {
|
|
34
|
+
throw new Error('plugin.json 必须声明稳定的小写插件 ID')
|
|
35
|
+
}
|
|
36
|
+
if (manifest.apiVersion !== 1) throw new Error('插件构建工具只支持 apiVersion: 1')
|
|
37
|
+
if (manifest.rendererFormat !== 'register') {
|
|
38
|
+
throw new Error('TSX 构建要求 plugin.json 声明 rendererFormat: "register"')
|
|
39
|
+
}
|
|
40
|
+
const addedAdeMajor = !Object.hasOwn(manifest, 'adeMajor')
|
|
41
|
+
if (!addedAdeMajor && (!Number.isSafeInteger(manifest.adeMajor) || manifest.adeMajor < 0 || manifest.adeMajor !== sdkMajor)) {
|
|
42
|
+
throw new Error(`plugin.json 的 adeMajor 必须是当前 SDK 对应的 ADE major 版本 ${sdkMajor}`)
|
|
43
|
+
}
|
|
44
|
+
if (addedAdeMajor) manifest.adeMajor = sdkMajor
|
|
45
|
+
if (manifest.styles !== undefined && (!Array.isArray(manifest.styles) || manifest.styles.some((style) => typeof style !== 'string'))) {
|
|
46
|
+
throw new Error('plugin.json 的 styles 必须是相对路径数组')
|
|
47
|
+
}
|
|
48
|
+
const entry = ownedPath(directory, options.entry ?? 'src/renderer.tsx', '渲染入口')
|
|
49
|
+
const output = ownedPath(directory, manifest.renderer, 'renderer')
|
|
50
|
+
const assetDirectory = path.relative(directory, path.dirname(output)).split(path.sep).filter(Boolean).map(encodeURIComponent).join('/')
|
|
51
|
+
const result = await build({
|
|
52
|
+
absWorkingDir: directory,
|
|
53
|
+
entryPoints: [entry],
|
|
54
|
+
outfile: output,
|
|
55
|
+
bundle: true,
|
|
56
|
+
write: false,
|
|
57
|
+
platform: 'browser',
|
|
58
|
+
format: 'cjs',
|
|
59
|
+
target: 'es2022',
|
|
60
|
+
jsx: 'automatic',
|
|
61
|
+
loader: assetLoaders,
|
|
62
|
+
assetNames: 'assets/[name]-[hash]',
|
|
63
|
+
publicPath: `ade-plugin://${pluginId}/${assetDirectory ? `${assetDirectory}/` : ''}`,
|
|
64
|
+
external: sharedModules,
|
|
65
|
+
metafile: true,
|
|
66
|
+
legalComments: 'none',
|
|
67
|
+
logLevel: 'silent',
|
|
68
|
+
plugins: [{
|
|
69
|
+
name: 'ade-browser-boundary',
|
|
70
|
+
setup(builder) {
|
|
71
|
+
builder.onResolve({ filter: /^node:/ }, ({ path: name }) => ({
|
|
72
|
+
errors: [{ text: `渲染插件不能引入 ${name};请在 main 入口通过命令提供此能力` }]
|
|
73
|
+
}))
|
|
74
|
+
}
|
|
75
|
+
}]
|
|
76
|
+
})
|
|
77
|
+
const code = result.outputFiles.find((file) => file.path === output)?.text
|
|
78
|
+
if (!code) throw new Error('未生成渲染插件文件')
|
|
79
|
+
// 浏览器用 <script> 装载工厂;不使用 eval 或 new Function,也不引入第二份 React。
|
|
80
|
+
const wrapped = `globalThis.__adeRegisterPlugin(${JSON.stringify(pluginId)}, function(require, module, exports) {\n${code}\n});\n`
|
|
81
|
+
// CSS 和 url() 引用的字体、图片也是预构建产物,不能只保留入口 JS。
|
|
82
|
+
const outputFiles = result.outputFiles.map((file) => ({
|
|
83
|
+
...file,
|
|
84
|
+
relative: path.relative(directory, file.path).split(path.sep).join('/')
|
|
85
|
+
}))
|
|
86
|
+
for (const file of outputFiles) ownedPath(directory, file.relative, '构建产物')
|
|
87
|
+
await Promise.all(outputFiles.map(async (file) => {
|
|
88
|
+
await fs.mkdir(path.dirname(file.path), { recursive: true })
|
|
89
|
+
await fs.writeFile(file.path, file.path === output ? wrapped : file.contents)
|
|
90
|
+
}))
|
|
91
|
+
const generatedStyles = outputFiles.filter((file) => file.path.endsWith('.css')).map((file) => file.relative)
|
|
92
|
+
const originalStyles = manifest.styles ?? []
|
|
93
|
+
const existingStyles = new Set(originalStyles.map((style) => ownedPath(directory, style, 'styles')))
|
|
94
|
+
const addedStyles = generatedStyles.filter((style) => !existingStyles.has(path.resolve(directory, style)))
|
|
95
|
+
if (addedStyles.length) {
|
|
96
|
+
manifest.styles = [...originalStyles, ...addedStyles]
|
|
97
|
+
}
|
|
98
|
+
if (addedAdeMajor || addedStyles.length) {
|
|
99
|
+
await fs.writeFile(path.join(directory, 'plugin.json'), `${JSON.stringify(manifest, null, 2)}\n`)
|
|
100
|
+
}
|
|
101
|
+
if (options.mainEntry) {
|
|
102
|
+
await build({
|
|
103
|
+
absWorkingDir: directory,
|
|
104
|
+
entryPoints: [ownedPath(directory, options.mainEntry, '主进程入口')],
|
|
105
|
+
outfile: ownedPath(directory, manifest.main, 'main'),
|
|
106
|
+
bundle: true,
|
|
107
|
+
platform: 'node',
|
|
108
|
+
format: 'cjs',
|
|
109
|
+
target: 'node24',
|
|
110
|
+
external: ['electron'],
|
|
111
|
+
plugins: [{
|
|
112
|
+
name: 'ade-main-sdk-runtime',
|
|
113
|
+
setup(builder) {
|
|
114
|
+
builder.onResolve({ filter: /^(?:@ai_mind\/(?:ade-)?plugin-sdk|@ade\/plugin-sdk)$/ }, () => ({ path: path.resolve(path.dirname(modulePath), '../index.mjs') }))
|
|
115
|
+
}
|
|
116
|
+
}],
|
|
117
|
+
legalComments: 'none',
|
|
118
|
+
logLevel: 'silent'
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
return { pluginId, output, files: outputFiles.map((file) => file.path), inputs: Object.keys(result.metafile.inputs) }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
const args = process.argv.slice(2)
|
|
126
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
127
|
+
console.log('用法:ade-plugin-build [插件目录] [--entry src/renderer.tsx] [--main-entry src/main.ts]')
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
let directory = '.'
|
|
131
|
+
let directorySeen = false
|
|
132
|
+
const options = {}
|
|
133
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
134
|
+
const arg = args[i]
|
|
135
|
+
if (arg === '--entry' || arg === '--main-entry') {
|
|
136
|
+
const value = args[++i]
|
|
137
|
+
if (!value || value.startsWith('--')) throw new Error(`${arg} 缺少文件路径`)
|
|
138
|
+
options[arg === '--entry' ? 'entry' : 'mainEntry'] = value
|
|
139
|
+
} else if (arg.startsWith('-')) throw new Error(`未知参数:${arg}`)
|
|
140
|
+
else if (!directorySeen) { directory = arg; directorySeen = true }
|
|
141
|
+
else throw new Error('只能指定一个插件目录')
|
|
142
|
+
}
|
|
143
|
+
const result = await buildPlugin(directory, options)
|
|
144
|
+
console.log(`已构建插件「${result.pluginId}」:${result.output}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// npm 的 .bin 命令是符号链接;Node 会将模块 URL 解析为真实文件地址。
|
|
148
|
+
const invokedPath = process.argv[1] ? await fs.realpath(path.resolve(process.argv[1])).catch(() => null) : null
|
|
149
|
+
if (invokedPath === modulePath) {
|
|
150
|
+
main().catch((error) => {
|
|
151
|
+
console.error(`插件构建失败:${error instanceof Error ? error.message : String(error)}`)
|
|
152
|
+
process.exitCode = 1
|
|
153
|
+
})
|
|
154
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type * as React from 'react'
|
|
2
|
+
import type * as ReactDOM from 'react-dom'
|
|
3
|
+
import type { AdeApi } from './types/api.js'
|
|
4
|
+
import type { PluginContext, SessionContext, ChatSession, ChatAdapter } from './types/adapter.js'
|
|
5
|
+
import type {
|
|
6
|
+
PluginCommandContext, PluginConfig, PluginDisposer, PluginSlotName, PluginViewInfo
|
|
7
|
+
} from './types/plugins.js'
|
|
8
|
+
|
|
9
|
+
export type * from './types/plugins.js'
|
|
10
|
+
export type * from './types/api.js'
|
|
11
|
+
export type * from './types/ade-types.js'
|
|
12
|
+
export type * from './types/adapter.js'
|
|
13
|
+
export type PluginManagementApi = AdeApi['plugins']
|
|
14
|
+
|
|
15
|
+
/** Component 在 ADE 的同一棵 React 树内运行。 */
|
|
16
|
+
export interface PluginComponentProps {
|
|
17
|
+
context: PluginCommandContext
|
|
18
|
+
config: PluginConfig
|
|
19
|
+
api: AdeApi
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PluginSlotRegistration {
|
|
23
|
+
id: string
|
|
24
|
+
component: React.ComponentType<PluginComponentProps>
|
|
25
|
+
order?: number
|
|
26
|
+
when?: (context: PluginCommandContext) => boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PluginViewRegistration extends PluginViewInfo {
|
|
30
|
+
component: React.ComponentType<PluginComponentProps>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RendererPluginContext {
|
|
34
|
+
pluginId: string
|
|
35
|
+
React: typeof React
|
|
36
|
+
ReactDOM: typeof ReactDOM
|
|
37
|
+
api: AdeApi
|
|
38
|
+
subscriptions: PluginDisposer[]
|
|
39
|
+
getContext(): PluginCommandContext
|
|
40
|
+
useContext(): PluginCommandContext
|
|
41
|
+
getConfig(): PluginConfig
|
|
42
|
+
useConfig(): PluginConfig
|
|
43
|
+
setConfig(patch: PluginConfig): Promise<PluginConfig>
|
|
44
|
+
onConfigChanged(listener: (config: PluginConfig) => void): PluginDisposer
|
|
45
|
+
registerView(view: PluginViewRegistration): PluginDisposer
|
|
46
|
+
registerSlot(name: PluginSlotName, contribution: PluginSlotRegistration): PluginDisposer
|
|
47
|
+
registerCommand(id: string, handler: (args: unknown, context: PluginCommandContext) => unknown | Promise<unknown>): PluginDisposer
|
|
48
|
+
/** 优先执行本插件 renderer 命令,其次执行本插件 main 命令;跨插件使用 plugin:ID:command。 */
|
|
49
|
+
executeCommand(id: string, args?: unknown): Promise<unknown>
|
|
50
|
+
on(event: string, listener: (data: unknown) => void): PluginDisposer
|
|
51
|
+
emit(event: string, data?: unknown): void
|
|
52
|
+
provideService(name: string, value: unknown): PluginDisposer
|
|
53
|
+
getService<T = unknown>(name: string): T | undefined
|
|
54
|
+
/** 接收清理函数,而不是 effect setup;注册项已经由宿主自动管理。 */
|
|
55
|
+
effect(cleanup: PluginDisposer): PluginDisposer
|
|
56
|
+
addStyle(css: string): PluginDisposer
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type PluginSessionContext = SessionContext
|
|
60
|
+
export type PluginChatSession = ChatSession
|
|
61
|
+
export type PluginChatAdapter = ChatAdapter
|
|
62
|
+
export type MainPluginContext = PluginContext
|
|
63
|
+
|
|
64
|
+
export interface RendererPlugin {
|
|
65
|
+
activate(context: RendererPluginContext): void | PluginDisposer | Promise<void | PluginDisposer>
|
|
66
|
+
deactivate?(): void | Promise<void>
|
|
67
|
+
}
|
|
68
|
+
export interface MainPlugin {
|
|
69
|
+
activate(context: MainPluginContext): void | PluginDisposer | Promise<void | PluginDisposer>
|
|
70
|
+
deactivate?(): void | Promise<void>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 带类型推导的插件定义;不会执行插件。 */
|
|
74
|
+
export function definePlugin<T extends RendererPlugin | MainPlugin>(plugin: T): T
|
|
75
|
+
|
|
76
|
+
/** 渲染插件约定的子扩展位置;pluginId 可筛选其拥有者。 */
|
|
77
|
+
export const Slot: React.ComponentType<{ name: PluginSlotName; pluginId?: string }>
|
package/index.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai_mind/ade-plugin-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ADE 可信插件 SDK、类型与 React 插件构建工具",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "sepmini",
|
|
7
|
+
"engines": { "node": ">=18" },
|
|
8
|
+
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./index.mjs",
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"exports": { ".": { "types": "./index.d.ts", "default": "./index.mjs" } },
|
|
13
|
+
"bin": { "ade-plugin-build": "./cli/build.mjs" },
|
|
14
|
+
"files": ["index.mjs", "index.d.ts", "types", "cli", "README.md", "LICENSE"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"sync:types": "node scripts/sync-types.mjs",
|
|
17
|
+
"release:preflight": "node scripts/release-preflight.mjs",
|
|
18
|
+
"release:dry-run": "node scripts/release-preflight.mjs --dry-run",
|
|
19
|
+
"prepublishOnly": "node scripts/release-preflight.mjs"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": { "esbuild": "^0.25.0" },
|
|
22
|
+
"peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" }
|
|
23
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// 由 sdk/scripts/sync-types.mjs 从 ADE 宿主生成,请勿手工修改。
|
|
2
|
+
import type { ChatAdapterInfo, ChatApprovalResponse, ChatAttachment, ChatAttachmentKind, ChatMessageRecord, CliModelOption, CliOption, StructuredEvent } from './ade-types.js';
|
|
3
|
+
import type { PluginCommandContext, PluginConfig, PluginDisposer } from './plugins.js';
|
|
4
|
+
export interface SessionContext {
|
|
5
|
+
id: string;
|
|
6
|
+
worktree: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
/** 会话所属 ADE 项目根目录;daemon 用于生成 Paseo projectKey。 */
|
|
9
|
+
projectPath?: string | null;
|
|
10
|
+
onData: (data: string) => void;
|
|
11
|
+
onExit: (code: number) => void;
|
|
12
|
+
onError: (err: Error) => void;
|
|
13
|
+
/**
|
|
14
|
+
* 底层 agent CLI 的会话标识(由 ChatManager 生成)。终端适配器用它
|
|
15
|
+
* 以固定会话启动/续接 TUI(如 qodercli --session-id / -r),使聊天视图
|
|
16
|
+
* 与终端视图共享同一会话、切换不丢内容。不支持时为 undefined。
|
|
17
|
+
*/
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
/** 是否为续接(该会话已有历史,spawn 时用 resume 而非创建) */
|
|
20
|
+
resume?: boolean;
|
|
21
|
+
/** 会话级 CLI 配置(用户在新会话对话框中选择的选项,如 --model) */
|
|
22
|
+
config?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
export interface ChatSession {
|
|
25
|
+
id: string;
|
|
26
|
+
write(data: string): void;
|
|
27
|
+
resize(cols: number, rows: number): void;
|
|
28
|
+
dispose(): void | Promise<void>;
|
|
29
|
+
/** 底层进程真正退出时完成;用于切换传输前等待文件锁/会话锁释放。 */
|
|
30
|
+
closed?: Promise<number>;
|
|
31
|
+
}
|
|
32
|
+
/** 结构化对话会话的上下文:除原始字节流外,额外提供解析后的事件通道 */
|
|
33
|
+
export interface StructuredSessionContext extends SessionContext {
|
|
34
|
+
onEvent: (ev: StructuredEvent) => void;
|
|
35
|
+
}
|
|
36
|
+
/** 启动一轮结构化对话的参数 */
|
|
37
|
+
export interface ChatTurnOptions {
|
|
38
|
+
prompt: string;
|
|
39
|
+
attachments?: ChatAttachment[];
|
|
40
|
+
/** CLI 会话标识:续接已有会话时传入(由 ChatManager 维护) */
|
|
41
|
+
sessionId: string | null;
|
|
42
|
+
/** 是否为续接(true 时用 resume 而非创建新会话) */
|
|
43
|
+
resume: boolean;
|
|
44
|
+
/** 会话级 CLI 配置(用户选择,如 --model / --permission-mode) */
|
|
45
|
+
config?: Record<string, string>;
|
|
46
|
+
}
|
|
47
|
+
/** provider 已持久化、可通过原生 session/thread id 继续的会话摘要。 */
|
|
48
|
+
export interface ProviderChatSession {
|
|
49
|
+
sessionId: string;
|
|
50
|
+
cwd: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
preview?: string;
|
|
53
|
+
createdAt: number;
|
|
54
|
+
updatedAt: number;
|
|
55
|
+
}
|
|
56
|
+
export interface ChatAdapter {
|
|
57
|
+
id: string;
|
|
58
|
+
label: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
kind: 'terminal' | 'sdk';
|
|
61
|
+
/** Spawn command, used by terminal adapters and available to plugins */
|
|
62
|
+
command?: string;
|
|
63
|
+
/** Plugin id that registered this adapter */
|
|
64
|
+
plugin?: string;
|
|
65
|
+
/**
|
|
66
|
+
* 该 CLI 的「YOLO / 跳过权限确认」参数;未声明表示不支持(设置页不提供开关)。
|
|
67
|
+
* 设置里按 Agent 开启后,新建会话时由 ChatManager 并入会话配置。
|
|
68
|
+
*/
|
|
69
|
+
yoloFlag?: string;
|
|
70
|
+
/** 结构化协议是否原生流式输出(渲染层据此决定打字机动画) */
|
|
71
|
+
streaming?: boolean;
|
|
72
|
+
/** 构造 --help 的 argv(有协议的适配器提供,如 codex 需 exec --help) */
|
|
73
|
+
helpArgs?: () => string[];
|
|
74
|
+
/** 核心配置项(显示在对话配置条上;其余动态选项在「更多配置」) */
|
|
75
|
+
coreOptions?: string[];
|
|
76
|
+
/** 模型列表命令 argv(如 qodercli --list-models);无则模型自由输入 */
|
|
77
|
+
listModelsArgs?: () => string[];
|
|
78
|
+
/** 把 CLI 模型列表解析为实际 value、显示名与分组。 */
|
|
79
|
+
parseModels?: (output: string) => CliModelOption[];
|
|
80
|
+
/** 对 help 未声明 choices 的选项提供建议值(如 qoder --reasoning-effort) */
|
|
81
|
+
suggestions?: Record<string, string[]>;
|
|
82
|
+
/**
|
|
83
|
+
* 该协议自己拥有、不应出现在配置界面的原生参数(会话续接、工程、日志、输出 schema 等)。
|
|
84
|
+
* 按 adapter 作用域排除,避免为了某个 CLI 而在全局黑名单里误伤其他 CLI 的同名参数。
|
|
85
|
+
*/
|
|
86
|
+
excludedOptions?: string[];
|
|
87
|
+
/** 通过 provider 原生协议补充/覆盖配置元数据(例如 Codex model/list)。refresh 为 true 时忽略缓存重新拉取。 */
|
|
88
|
+
loadOptions?: (refresh?: boolean) => Promise<CliOption[]>;
|
|
89
|
+
createSession(ctx: SessionContext): ChatSession | Promise<ChatSession>;
|
|
90
|
+
/**
|
|
91
|
+
* 原生对话能力(可选):适配器支持结构化事件流(如 qodercli stream-json)
|
|
92
|
+
* 时实现它,聊天面板可渲染原生气泡界面。每轮对话由 ChatManager 调用一次,
|
|
93
|
+
* 进程输出经解析后通过 ctx.onEvent 广播;进程退出(或异常)调 onExit。
|
|
94
|
+
*/
|
|
95
|
+
chat?: {
|
|
96
|
+
/**
|
|
97
|
+
* 会话标识是否由应用预分配(qoder/claude/pi 支持 --session-id 创建指定
|
|
98
|
+
* 会话)。false 表示会话 id 由 CLI 生成(如 codex thread_id),首轮后
|
|
99
|
+
* 通过 init 事件回填,续接必须等回填完成。
|
|
100
|
+
*/
|
|
101
|
+
preassignedSessionId?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* 在首轮消息前预创建 provider 会话。返回的 id 只代表已分配;在首个 turn
|
|
104
|
+
* 持久化前,不能假定终端 CLI 已经可以 resume 它。
|
|
105
|
+
*/
|
|
106
|
+
prepareSession?: (ctx: StructuredSessionContext, config?: Record<string, string>) => Promise<{
|
|
107
|
+
sessionId: string;
|
|
108
|
+
}>;
|
|
109
|
+
/** 图形化连接让位给 TUI 前释放 provider 连接。 */
|
|
110
|
+
detach?: (id: string) => void | Promise<void>;
|
|
111
|
+
/** 从 provider 原生历史重建气泡消息(终端交互后回到图形界面时使用)。 */
|
|
112
|
+
readHistory?: (ctx: StructuredSessionContext, sessionId: string, config?: Record<string, string>) => Promise<ChatMessageRecord[]>;
|
|
113
|
+
/** 发现本地 CLI 已持久化、可 resume 的会话。 */
|
|
114
|
+
discoverSessions?: (cwds: string[]) => Promise<ProviderChatSession[]>;
|
|
115
|
+
/** 活跃 turn 支持追加指令时使用;不支持的适配器由 ChatManager 排队。 */
|
|
116
|
+
steer?: (id: string, prompt: string, attachments?: ChatAttachment[]) => Promise<void>;
|
|
117
|
+
/** 回复审批或运行时用户输入请求。 */
|
|
118
|
+
respond?: (id: string, requestId: string, response: ChatApprovalResponse) => Promise<void>;
|
|
119
|
+
/** 永久关闭适配器为该 ADE 会话保留的资源。 */
|
|
120
|
+
disposeSession?: (id: string) => void | Promise<void>;
|
|
121
|
+
/** 实际可发送的附件类型;目录始终作为提示词中的路径引用。 */
|
|
122
|
+
attachmentKinds?: ChatAttachmentKind[];
|
|
123
|
+
/** 旧插件兼容字段;未声明 attachmentKinds 时 true 视为支持 image/file。 */
|
|
124
|
+
attachmentSupported?: boolean;
|
|
125
|
+
/** 启动一轮结构化对话 */
|
|
126
|
+
startTurn(ctx: StructuredSessionContext, opts: ChatTurnOptions): ChatSession | Promise<ChatSession>;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export interface PluginHost {
|
|
130
|
+
registerAdapter(adapter: ChatAdapter): void;
|
|
131
|
+
log(level: 'info' | 'warn' | 'error', message: string): void;
|
|
132
|
+
}
|
|
133
|
+
/** 可信本地插件的主进程上下文;所有注册项在停用时自动撤销。 */
|
|
134
|
+
export interface PluginContext extends PluginHost {
|
|
135
|
+
pluginId: string;
|
|
136
|
+
pluginPath: string;
|
|
137
|
+
dataPath: string;
|
|
138
|
+
subscriptions: PluginDisposer[];
|
|
139
|
+
registerCommand(id: string, handler: (args: unknown, context: PluginCommandContext) => unknown | Promise<unknown>): PluginDisposer;
|
|
140
|
+
provideService(name: string, service: Record<string, unknown>): PluginDisposer;
|
|
141
|
+
getService(name: string): unknown;
|
|
142
|
+
events: {
|
|
143
|
+
on(name: string, listener: (payload: unknown) => void): PluginDisposer;
|
|
144
|
+
emit(name: string, payload?: unknown): void;
|
|
145
|
+
};
|
|
146
|
+
getConfig(): PluginConfig;
|
|
147
|
+
setConfig(patch: PluginConfig): Promise<PluginConfig>;
|
|
148
|
+
onConfigChanged(listener: (config: PluginConfig) => void): PluginDisposer;
|
|
149
|
+
/** 登记定时器、文件监听等资源的清理函数。 */
|
|
150
|
+
effect(disposer: PluginDisposer): PluginDisposer;
|
|
151
|
+
}
|
|
152
|
+
export type PluginMain = (host: PluginHost) => void | Promise<void>;
|
|
153
|
+
export declare function adapterToInfo(a: ChatAdapter): ChatAdapterInfo;
|