Kunkun
构建扩展

服务

暴露带类型和 schema 验证的方法,供其他扩展和 AI 代理调用。

服务 是你的扩展暴露的一组可调用方法。其他扩展通过服务代理调用它们,内置的 AI 代理可以将其作为工具调用。服务将你的扩展转化为可重用的基础设施——OCR 引擎、FFmpeg 封装器、YouTube 下载器。

契约优先模式

真实的服务扩展(ffmpegyoutube-tools)在提供者和消费者之间共享一个契约模块,这样双方在编译时就方法名称、输入和输出达成一致。

1. 定义契约(共享)

// src/contract.ts
import * as v from "valibot";
import { defineServiceContract, method, type InferService } from "@kunkunsh/sdk/contract";

export const mathContract = defineServiceContract({
  id: "com.you.math",
  serviceName: "math",
  description: "基础数学运算",
  methods: {
    add: method({
      description: "两个数字相加",
      input: v.object({ a: v.number(), b: v.number() }),
      output: v.object({ result: v.number() }),
    }),
  },
});

export type MathService = InferService<typeof mathContract>;

从专用子路径导出,供消费者导入:

{ "exports": { "./contract": { "types": "./dist/contract.d.ts", "default": "./dist/contract.js" } } }

2. 实现(提供者)

implementService 将你的处理程序连接到契约,并自动验证输入/输出:

// src/MathService.ts
import { implementService } from "@kunkunsh/sdk/runtime";
import { mathContract } from "./contract";

export default implementService(mathContract, {
  async add({ a, b }) {
    return { result: a + b };
  },
  async onInit() { /* 打开资源 */ },
  async onDestroy() { /* 清理 */ },
});

使用 service 模式构建:

await Bun.build({
  entrypoints: ["./src/MathService.ts"],
  outdir: "./dist/node",
  target: "node", format: "esm", minify: true,
  plugins: [kunkunCommandPlugin({ mode: "service" }) as BunPlugin],
});

3. 在清单中声明

services[] 数组是商店和代理可见的内容(methods 镜像你的契约;JSON schema 驱动验证和 AI 工具描述):

{
  "kunkun": {
    "services": [
      {
        "name": "math",
        "description": "基础数学运算",
        "main": "dist/node/MathService.js",
        "serviceMode": "node-headless",
        "methods": [
          {
            "name": "add",
            "description": "两个数字相加",
            "inputSchema": { "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "number" } }, "required": ["a", "b"] },
            "outputSchema": { "type": "object", "properties": { "result": { "type": "number" } } }
          }
        ]
      }
    ]
  }
}

消费服务

三个层级,从最松散到最安全:

第一层——无类型(无需导入):

import { getHostAPI } from "@kunkunsh/sdk/runtime";
const { result } = await getHostAPI().services.call("com.you.math", "math", "add", { a: 5, b: 3 });

第二层——编译时类型(零运行时开销):

import { createServiceClient } from "@kunkunsh/sdk/runtime";
import type { MathService } from "com.you.math/contract";

const math = createServiceClient<{ math: MathService }>("com.you.math");
const { result } = await math.math.add({ a: 5, b: 3 }); // 完全类型化

第三层——类型 + 运行时验证(发送前验证输入,接收后验证输出):

import { createValidatedServiceClient } from "@kunkunsh/sdk/runtime";
import { mathSchemas } from "com.you.math/contract";

const math = createValidatedServiceClient("com.you.math", mathSchemas);
const { result } = await math.math.add({ a: 5, b: 3 });

声明依赖

使用 serviceDependencies 预批准提供者(这样调用时不会弹出提示):

{
  "kunkun": {
    "serviceDependencies": [
      { "extensionIdentifier": "com.you.math", "service": "math" }
    ]
  }
}

未声明的调用会通过代理提示用户批准。

权限组合

服务调用组合权限,这样调用者不会继承提供者的私有细节:

  • 提供者 提供自己的实现权限(例如 FFmpeg 对 ffmpeg 二进制的限定 shell 访问)。
  • 调用者 提供用户选择的输入的资源权限(例如要转换文件的 fs-read/fs-write)。

因此 video-processing 可以在用户选择的文件上调用 ffmpeg.convertImage,而无需声明对 ffmpegshell 访问权限。参见权限 → 交集

AI 代理桥接

每个服务方法自动注册为一个 AI 工具,名称为:

plugin__<extensionIdentifier>__<serviceName>__<methodName>

安装一个服务扩展后,代理就可以调用它——一个描述良好的 OCR 服务意味着"提取此图片中的文字"直接生效。

选项与生命周期

await math.math.add({ a: 1, b: 2 }); // 使用默认 30s 超时
// 每次调用:createServiceClient(id, { timeout: 5000 })
  • 默认超时 30s,最长 10 分钟。
  • onInit / onDestroy 钩子在服务 worker 启动/停止时运行(onDestroy 有约 5s 硬超时)。

实际示例

  • ffmpeg(提供者)↔ video-processing(消费者)——图片/视频转换
  • youtube-tools(提供者)↔ youtube-podcast-generator(消费者)——元数据、转录、下载

On this page