Plugin

自定义插件

编写、发布并配置可安装的 Downcity Plugin

自定义插件

一个第三方目录只定义一个 Plugin。Plugin 的全局唯一 ID 同时是最终目录名、Agent 引用键和运行时 Registry key。

开发目录

github-plugin/
├── plugin.json
├── package.json
├── README.md
├── icon.svg
├── src/
│   ├── plugin.ts
│   └── setup.ts
├── tsconfig.json
└── dist/setup.js

src/tsconfig.json 只服务本地开发。Downcity 不规定构建工具,也不需要 tsup.config.tspackage.json 属于 Plugin 包,用来声明明确的 ESM package 边界,因此必须包含 "type": "module" 并会随 Plugin 安装。构建脚本只需把入口及其运行时依赖打包成一个自包含 ESM 文件;例如可直接在 package.json 中调用 esbuild:

{
  "type": "module",
  "scripts": {
    "build": "esbuild src/setup.ts --bundle --platform=node --format=esm --outfile=dist/setup.js"
  }
}

SDK Plugin Class 不受 CLI 配置协议限制。构造参数完全由 Plugin 自己决定;需要 Provider、Client 或本地适配器时,直接在 SDK 中传入:

import type { Plugin, PluginActions } from "@downcity/agent";

export interface GithubPluginConfig {
  api_url: string;
  token: string;
}

export class GithubPlugin implements Plugin {
  readonly name = "github";
  readonly title = "GitHub";
  readonly description = "GitHub 集成";
  readonly actions: PluginActions;

  constructor(config: GithubPluginConfig) {
    this.actions = {};
  }
}

SDK 用户直接装配实例,Agent API 保持简单:

const plugin = new GithubPlugin({
  api_url: "https://api.github.com",
  token: process.env.GITHUB_TOKEN ?? "",
});

const agent = new Agent({
  id: "coding-pro",
  model,
  plugins: [plugin],
});

实例 nameplugin.jsonid、目录名和 Agent 引用必须一致。SDK 不要求 Plugin 导出固定 constructor,也不要求 constructor 接收 profile。

Plugin 定义

源目录只保留一个 plugin.json

{
  "schema_version": 1,
  "id": "github",
  "version": "1.0.0",
  "title": "GitHub",
  "description": "GitHub 集成",
  "icon": "./assets/github.svg",
  "setup": "dist/setup.js"
}

setup 指向一个自包含 ESM 模块。该模块导出配置 schema 和宿主装配函数:

import type { PluginHostContext } from "@downcity/agent";

export const schema = {
  type: "object",
  properties: {
    api_url: { type: "string", format: "uri" },
    token: { type: "string", minLength: 1, writeOnly: true },
  },
  required: ["api_url", "token"],
  additionalProperties: false,
} as const;

export function setup(context: PluginHostContext): GithubPlugin {
  return new GithubPlugin(context.profile as GithubPluginConfig);
}

setup 是 City 的装配边界,不是安装脚本,也不是 Plugin 生命周期。每次调用都必须创建新的 Plugin 实例;context.embassycontext.data_pathcontext.extensions 只代表当前宿主提供的能力。context.data_path 是运行时私有目录,不用于保存 profile 配置;profile 由 City 在 ~/.downcity/plugins/<plugin_id>/config.toml 中统一管理,多个 Agent 可以引用同一个 profile。需要登录态的 Plugin 应显式检查 context.embassy 是否存在。

icon 可选,支持 http://https:// 远程地址,或 Plugin 根目录内的相对路径。相对路径对应的文件会随安装复制,并且不能使用 symlink 或路径逃逸。

README.md 是第三方 Plugin 的必需文件,安装后会保留在 Plugin 目录,供 CLI、Desktop 和用户离线查看。

配置 schema 使用 JSON Schema 2020-12。常用字段包括:

  • typepropertiesrequiredadditionalProperties
  • enumconstoneOfanyOfallOf
  • 字符串的 minLengthmaxLengthpatternformat
  • 数值的 minimummaximummultipleOf
  • 数组的 itemsminItemsmaxItemsuniqueItems
  • defaultexamplestitledescription
  • 敏感字段使用 writeOnly: true

例如:

{
  "type": "object",
  "properties": {
    "endpoint": {
      "type": "string",
      "format": "uri"
    },
    "mode": {
      "type": "string",
      "enum": ["safe", "fast"],
      "default": "safe"
    },
    "token": {
      "type": "string",
      "minLength": 1,
      "writeOnly": true
    }
  },
  "required": ["endpoint", "token"],
  "additionalProperties": false
}

表单展示所需的占位符、排序或控件提示放在 x_downcity 下,不改变 JSON Schema 的运行时校验语义。

字段级 default 只作为 JSON Schema 注解,供新 Profile 表单初始化,不会自动写入未选择 Profile 的运行时配置。Schema 的唯一事实源是 setup 模块;plugin.json 不重复保存 configschemadefaults

setup 必须指向单个自包含 ESM 文件。安装器不会执行依赖安装、构建脚本或调用 setup(),也不会复制入口之外的源码和开发文件。Plugin 使用 Zod 等运行时依赖时,构建工具必须把依赖打进入口。

安装后的目录是确定的:

~/.downcity/plugins/github/
├── plugin.json
├── config.toml
├── package.json
├── README.md
├── assets/github.svg
└── dist/setup.js

config.toml 只保存命名的明文 profile 值,文件权限为 0600。它属于 Plugin 的全局配置,不复制到 Agent 目录。CLI 与 Desktop 在需要配置或装配时加载 setup 模块的 schema,构建表单并脱敏 writeOnly 字段。普通安装和 Plugin 列表不会调用 setup()。Plugin 的运行时状态和缓存使用 context.data_path,CLI 与 Desktop 按 Agent 和 Plugin 隔离,不因进入不同 Workspace 而复制。

schema_version = 1

[profiles.production]
api_url = "https://api.github.com"
token = "plain-local-token"

Agent 的 Plugin 引用有两种运行语义:没有显式 profile 时直接使用空对象 {},永远不会隐式读取名为 default 的 Profile;显式选择 profile 时读取该全局 Profile,不存在则报错。两种配置都会执行 setup 模块 Schema 校验。Schema 接受 {} 的 Plugin 可以直接启用,Schema 要求字段的 Plugin 必须先创建并显式选择 Profile。

安装与配置

city plugin install ./github-plugin
city plugin config github production --interactive
city plugin enable github my-agent --profile production
city plugin update github
city plugin uninstall github

更新会原子替换整个 Plugin 目录并保留 config.toml。新 Schema 无法校验已有 profile 时更新失败;仍被 Agent 引用的 profile 或 Plugin 不能删除。

安装过程只读取并复制 plugin.jsonpackage.json 与声明的 setup,不会执行 npm install、构建脚本、Plugin lifecycle 或调用 setup()。City 在配置或 Agent 装配时读取 schema 并验证 setup 导出;Agent 真正装配时再调用 setup(context) 并验证实例 ID。package.json 与 setup 会生成 SHA-256 摘要。只应安装可信来源。

继续阅读: