LingXiPluginSDK
为 LingXiAgent 编写进程外(out-of-process)原生插件的官方 Swift Package。声明交互式 Slash 命令、扩展模型可调用的 Tool、订阅生命周期 Hook,并只读观察宿主推送的运行时快照。SDK 不含 Core:插件是独立可执行文件,通过 stdin/stdout 上的 JSON Lines IPC 与运行中的 LingXiAgent 通信。
Declaration
import LingXiPluginSDK
Overview
LingXiPluginSDK 是 LingXiAgent 的官方扩展开发套件。它不是提示词包装器,也不是脚本解释器:你用现代 Swift 写一个独立可执行文件,类型安全,进程独立。SDK 里只有协议、DTO 与 IPC 驱动——没有会话存储、没有权限引擎、没有 Provider 运行时,因此插件返回值再离谱也不会弄坏用户正在进行的会话:你在另一个进程里。
设计上遵循两条边界:
- 进程隔离,而非动态注入:插件以独立子进程运行,Core 与插件之间用 stdin/stdout 上的换行分隔 JSON(JSON Lines IPC)通信,不使用
dlopen,因此插件不在 Core 的地址空间里读它的内存; - 只读感知,不接管调度:插件可以读取宿主推送的运行时快照(会话 Token 水位、P-Core / E-Core 计数、耗时指标、工作区状态),但不能改写 Agent 的调度与会话状态——主驾驶员始终是 Client / TUI。
Plugin Lifecycle
~/.lingxiagent/plugins/,或项目级 <project>/.lingxi/plugins/,LingXiAgent 启动时自动发现并校验清单。它必须是当前平台可执行的二进制:macOS / Linux 直接用文件名,Windows 复制 my-plugin.exe。
Quick Start
只需 3 步,即可完成一个生产级 Swift 插件的开发与部署。
1 创建 Swift Package 并声明 LingXiPluginSDK 依赖
// swift-tools-version:6.0
import PackageDescription
let package = Package(
name: "MyFirstPlugin",
platforms: [.macOS(.v13)],
dependencies: [
.package(url: "https://github.com/LingXiFox/LingXiPluginSDK.git", from: "0.1.0")
],
targets: [
.executableTarget(
name: "my-plugin",
dependencies: [
.product(name: "LingXiPluginSDK", package: "LingXiPluginSDK")
]
)
]
)
LingXiPluginSDK 是独立仓库与独立 SemVer 的 MIT Swift Package,不需要拉取 LingXiAgent 仓库即可编译插件。
2 编写 main.swift:实现 LingXiPlugin 协议
import Foundation
import LingXiPluginSDK
@main
struct MyFirstPlugin: LingXiPlugin {
init() {}
var manifest: PluginManifest {
PluginManifest(
id: "com.example.my-plugin",
name: "My Plugin",
version: "0.1.0",
description: "演示命令注册与宿主运行时快照读取",
capabilities: [.projectRead]
)
}
func activate(context: PluginContext) async throws {
context.registerTool(EchoTool())
context.registerCommand(StatusCommand())
context.on(.sessionStart) { payload in
context.logger.info("session started: \(payload.subjectID)")
}
}
}
struct EchoTool: PluginTool {
var name: String { "echo" }
var description: String { "Echoes its argument back." }
func execute(arguments: String, context: ToolExecutionContext) async throws -> String {
arguments
}
}
struct StatusCommand: PluginCommand {
var name: String { "status" }
var description: String { "Reports what the host published." }
func execute(args: [String], context: CommandExecutionContext) async throws -> PluginCommandResult {
guard let workspace = try? await context.info.getWorkspaceInfo() else {
return .message("宿主尚未推送运行时快照", presentation: .inline)
}
return .message("工作区 \(workspace.rootPath)(\(workspace.currentGitBranch ?? "非 Git"))",
presentation: .modal,
title: "Workspace")
}
}
这份示例与 SDK 仓库里的 DocumentationExampleCompileTests 同源:文档片段是被编译验证过的,不是设想出来的 API。
3 编译并一键投递二进制到插件目录
# 编译 Release 二进制
swift build -c release
# 投递到 LingXiAgent 全局插件目录(或项目级 .lingxi/plugins/)
mkdir -p ~/.lingxiagent/plugins
cp .build/release/my-plugin ~/.lingxiagent/plugins/
# 在 TUI 中输入 /plugins 查看加载状态,敲击 /status 体验居中弹出窗口
平台差异:macOS 与 Linux 拷贝 .build/release/my-plugin;Windows 使用 swift build -c release 产出的 my-plugin.exe。插件必须是宿主进程能直接 exec 的原生二进制,不能是脚本包装。
Topics
Plugin Lifecycle & Manifest
外部插件的入口协议。声明插件元数据,并在 activate(context:) 中注册 Commands、Tools 与 Hooks。
id · name · version · description · author? · capabilities · minimumCoreVersion?
包含插件 ID、显示名称、语义化版本号、作者以及申请的权限能力列表。能力声明与协议兼容是两件事:兼容性由握手里的 ipcVersion 判定,minimumCoreVersion 只是给人看的提示,从不参与线上校验。
声明插件所需的权限能力:.projectRead(只读工作区)、.projectWrite、.networkAccess 等。
Slash Commands & Presentation
扩展用户在终端交互输入框直接敲击的斜杠命令(例如 /fox-info),支持入参、别名与分类。
控制命令执行结果在客户端呈现的物理形态:.modal(居中独立弹出浮层,支持上下滚动与 Esc 关闭)或 .inline(行内时间轴卡片)。
命令执行的返回值:.message(text, presentation:, title:) 本地卡片免消耗 Token 输出,或 .prompt(text) 动态构造指令唤醒 Agent 思考。
High-Dimensional Context Sensing (Read-Only Info Hub)
只读信息枢纽,经 context.info 访问。四个段落全部来自 LingXiAgent Core 推送的 runtime snapshot(host.snapshot):SDK 自己不测量、不估算、不设默认值。宿主未推送该段落时抛 PluginInfoUnavailable(field:),而不是返回 idle / unknown / 0。
读取宿主推送的 P-Core / E-Core 计数:P 侧驻留 Token、E-Core 对象数与引用数、最近一次淘汰触发原因、思考等级、后台任务数。P-Core 的淘汰由 P 侧保留策略决定;E-Core heat 只服务召回与观测,不驱动 P eviction。
读取会话级状态:活跃模型 ID、累计 Token、上下文窗口占比、消息数、是否压缩过、近期轮次摘要。字段可为空——空值表示宿主该刻没有此项,不是 0。
读取性能指标:首字延迟 (TTFT)、思考耗时、工具执行耗时、Provider 平均延迟与是否被限流。Core 未发布的段落会抛 PluginInfoUnavailable,SDK 不会用 0 填充。
读取工作区快照:根路径、是否为 Git 仓库、当前分支、脏文件计数、主要语言分布与 Core 版本号。
Extending Autonomous Model Tools
声明供大模型在 ReAct 思考与工具循环中自主决策调用的 Native Tool。需提供 JSON Schema 参数规范。
sessionID: String · toolCallID: String · logger: PluginLogger
工具调用的执行上下文。注意它不带 info 句柄:Tool 只做输入→输出,需要读宿主状态请写在 PluginCommand 里(见 CommandExecutionContext)。
sessionID: String? · info: PluginInfoHub · logger: PluginLogger
斜杠命令的执行上下文,是插件读取 context.info 宿主快照的入口。sessionID 可空:命令可能在无会话状态下被调用,此时宿主只推送工作区段落。
Security Model
下面这张表只写当前代码确实实现并由测试覆盖的层。把「进程隔离」写成「操作系统沙箱」是对使用者的误导,因此本页同时列出这套机制不提供的东西。
- 没有 OS 级 syscall / 文件系统沙箱:插件进程仍以运行 LingXiAgent 的用户权限执行,可以直接调用
FileManager或发起网络连接。 - Capability 是宿主侧的准入策略,不是内核边界:它约束宿主愿意与这个插件协作的范围,不拦截插件自己发起的 syscall。
- 因此本页不写「任何越界访问一定会被拦截」这类断言;需要硬隔离时,请把插件跑在容器或独立用户下,这属于部署层职责。
capabilities 声明视为「这个插件打算做什么」的线索,而不是运行时的强制上限。
LingXi Plugin IPC · JSON Lines 协议规范
官方 SDK 用 Swift,但插件协议本身与语言无关:任何能读写 stdin/stdout 的语言都可以实现插件二进制,只要遵守下面的帧格式与方法名。
方法名的唯一权威来源是 SDK 里的 PluginIPC.Method,文档不能自创名字。
1. 帧格式与方向
- 载体通道:Core 与插件通过子进程的
stdin / stdout通信,不开网络端口; - 分帧:一行一个 JSON 对象,以
\\n定界(JSON Lines / newline-delimited JSON); - 请求:
{"id": "...", "method": "...", "params": ...};响应:{"id": "...", "result": ..., "error": null}; - 不是 JSON-RPC 2.0:线上报文没有
"jsonrpc": "2.0"字段,也没有批量与 notification 语义。所有调用都是 Core → Plugin 的单向请求/响应,运行时信息通过host.snapshot由宿主推送,插件不反向发起 RPC。
调用顺序固定为 spawn → host.snapshot → plugin.initialize → activate(context:);此后每次 tool.execute / command.execute / hook.emit 之前都会先刷新一次快照。
2. 报文样例(与真实 Codable DTO 一致)
{"id":"snap-1","method":"host.snapshot","params":{
"observedAt":"2026-10-01T08:00:00Z",
"ipcVersion":1,
"workspace":{"rootPath":"/work/demo","isGitRepository":true,"currentGitBranch":"main","dirtyFileCount":2,"primaryLanguages":["Swift"],"coreVersion":"1.1.0"},
"peCore":{"pCoreTokens":18420,"eCoreObjects":37,"eCoreReferences":52,"lastEvictionTrigger":"pCoreSoftLimit","reasoningEffort":"high","backgroundTaskCount":1},
"contextState":{"activeModelID":"gpt-5.6-luna","totalTokenUsage":15400,"contextWindowPercentage":0.12,"isCompacted":false,"messageCount":8},
"performance":null
}}
段落缺省或为 null 就代表宿主此刻没有这项事实:读取它会抛 PluginInfoUnavailable,SDK 不会填 idle / 0。上面的 performance:null 就是这种情况。
// 1. Core 发起握手(带上宿主自己的 IPC 版本)
{"id":"init-1","method":"plugin.initialize","params":{"hostIPCVersion":1,"coreVersion":"1.1.0"}}
// 2. 插件回包:manifest + 自己支持的 ipcVersion + tools + commands
{"id":"init-1","result":{
"ipcVersion":1,
"manifest":{
"id":"com.example.rust-plugin",
"name":"Rust 诊断扩展",
"version":"1.0.0",
"description":"调用本地分析引擎做安全体检",
"author":"example",
"capabilities":["projectRead"],
"minimumCoreVersion":"1.1.0"
},
"tools":[{"name":"rust_audit","description":"Scan a path and report issues.","inputSchema":"{\"type\":\"object\",\"properties\":{\"path\":{\"type\":\"string\"}}}"}],
"commands":[{"name":"rust-info","aliases":["ri"],"description":"Show Rust plugin status.","category":"Diagnostics","argumentHint":""}]
},"error":null}
兼容性由 ipcVersion 协商:双方没有共同支持版本时,握手阶段直接失败并终止进程,而不是等到某个 command 解码时才炸。minimumCoreVersion 只是人类可读提示,从不参与线上判定。
// 1. 模型在推理循环里调用插件工具;arguments 是原始 JSON 字符串,由插件自己解释 inputSchema
{"id":"tool-88","method":"tool.execute","params":{
"toolName":"rust_audit",
"arguments":"{\"path\":\"Sources/\"}",
"sessionID":"sess-abc-123",
"toolCallID":"call_9901"
}}
// 2. 插件返回纯文本(或文本化的 JSON 结论)
{"id":"tool-88","result":"{\"issues\":0,\"scannedFiles\":42,\"status\":\"clean\"}","error":null}
// 1. 用户输入 /rust-info --verbose
{"id":"cmd-42","method":"command.execute","params":{
"commandName":"rust-info",
"arguments":["--verbose"],
"sessionID":"sess-abc-123"
}}
// 2. presentation 取 "modal" 或 "inline";isPrompt=true 时文本作为提示词展开
{"id":"cmd-42","result":{
"isPrompt":false,
"text":"• scanned files: 42\n• issues: 0",
"presentation":"modal",
"title":"Rust 审计诊断报告"
},"error":null}
// 事件取值:sessionStart · sessionEnd · agentTurnStart · agentTurnEnd · toolBefore · toolAfter
{"id":"hook-7","method":"hook.emit","params":{"event":"sessionStart","subjectID":"sess-abc-123","metadata":{"workspace":"/work/demo"}}}
{"id":"hook-7","result":null,"error":null}
- 失败以响应形式返回:
{"id":"...","result":null,"error":"message"};宿主把它转成一次失败的调用,不影响会话存续。 - 未知方法、缺少 params、载荷畸形都会得到错误响应,插件主循环本身不应崩溃。
- stdin 关闭(EOF)是唯一的正常退出信号:插件应在此调用
deactivate()后退出。 - 超时默认
3.0s;长任务请挪到后台执行,请求路径只返回一个任务句柄,不要占住这次调用。
Compatibility & Versioning
对不推送快照的旧宿主:tools 与 commands 照常工作,context.info 抛 PluginInfoUnavailable——不会出现编造的指标。SDK、Agent、Model Catalog 三者版本各自独立演进,不做同步锁版。