@vibearound/plugin-channel-sdk 0.1.2 → 0.4.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/README.md +62 -108
- package/dist/advanced.d.ts +19 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +18 -0
- package/dist/advanced.js.map +1 -0
- package/dist/connection.d.ts +10 -4
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +10 -4
- package/dist/connection.js.map +1 -1
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +33 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +27 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -41
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +80 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +131 -0
- package/dist/plugin.js.map +1 -0
- package/dist/renderer.d.ts +52 -46
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +131 -88
- package/dist/renderer.js.map +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/advanced.ts +43 -0
- package/src/connection.ts +10 -4
- package/src/errors.ts +29 -0
- package/src/index.ts +48 -52
- package/src/plugin.ts +247 -0
- package/src/renderer.ts +199 -94
- package/src/types.ts +24 -0
package/src/index.ts
CHANGED
|
@@ -1,79 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vibearound/plugin-channel-sdk
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* SDK for building VibeAround channel plugins.
|
|
5
5
|
*
|
|
6
6
|
* ## Quick start
|
|
7
7
|
*
|
|
8
8
|
* ```ts
|
|
9
9
|
* import {
|
|
10
|
-
*
|
|
10
|
+
* runChannelPlugin,
|
|
11
11
|
* BlockRenderer,
|
|
12
|
-
*
|
|
13
|
-
* type
|
|
14
|
-
* type SessionNotification,
|
|
12
|
+
* type ChannelBot,
|
|
13
|
+
* type BlockKind,
|
|
15
14
|
* } from "@vibearound/plugin-channel-sdk";
|
|
16
15
|
*
|
|
17
16
|
* class MyRenderer extends BlockRenderer<string> {
|
|
18
|
-
* protected async
|
|
19
|
-
*
|
|
20
|
-
* return msg.id;
|
|
21
|
-
* }
|
|
22
|
-
* protected async editBlock(channelId, ref, kind, content, sealed) {
|
|
23
|
-
* await myPlatform.edit(ref, content);
|
|
24
|
-
* }
|
|
17
|
+
* protected async sendText(chatId: string, text: string) { ... }
|
|
18
|
+
* protected async sendBlock(chatId: string, kind: BlockKind, content: string) { ... }
|
|
25
19
|
* }
|
|
26
20
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
21
|
+
* runChannelPlugin({
|
|
22
|
+
* name: "vibearound-mybot",
|
|
23
|
+
* version: "0.1.0",
|
|
24
|
+
* requiredConfig: ["bot_token"],
|
|
25
|
+
* createBot: ({ config, agent, log, cacheDir }) => new MyBot(...),
|
|
26
|
+
* createRenderer: (bot, log, verbose) => new MyRenderer(bot, log, verbose),
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* ## Advanced / low-level usage
|
|
29
31
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* (a) => {
|
|
33
|
-
* agent = a;
|
|
34
|
-
* return {
|
|
35
|
-
* sessionUpdate: async (n) => renderer.onSessionUpdate(n),
|
|
36
|
-
* requestPermission: async (p) => ({
|
|
37
|
-
* outcome: { outcome: "selected", optionId: p.options![0].optionId },
|
|
38
|
-
* }),
|
|
39
|
-
* extNotification: async (method, params) => {
|
|
40
|
-
* switch (normalizeExtMethod(method)) {
|
|
41
|
-
* case "channel/system_text":
|
|
42
|
-
* await myPlatform.send(params.channelId as string, params.text as string);
|
|
43
|
-
* break;
|
|
44
|
-
* }
|
|
45
|
-
* },
|
|
46
|
-
* };
|
|
47
|
-
* },
|
|
48
|
-
* );
|
|
32
|
+
* For plugins that need custom ACP lifecycle control (e.g. weixin-openclaw-bridge),
|
|
33
|
+
* import from the `advanced` subpath:
|
|
49
34
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* await conn.closed;
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { connectToHost } from "@vibearound/plugin-channel-sdk/advanced";
|
|
53
37
|
* ```
|
|
54
38
|
*/
|
|
55
39
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// High-level API — what plugin developers use
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
// Entry point
|
|
45
|
+
export { runChannelPlugin } from "./plugin.js";
|
|
59
46
|
|
|
60
|
-
//
|
|
47
|
+
// Base class for stream rendering
|
|
61
48
|
export { BlockRenderer } from "./renderer.js";
|
|
62
49
|
|
|
63
|
-
//
|
|
50
|
+
// Interfaces the plugin implements
|
|
51
|
+
export type {
|
|
52
|
+
ChannelBot,
|
|
53
|
+
ChannelPluginLogger,
|
|
54
|
+
CreateBotContext,
|
|
55
|
+
RunChannelPluginSpec,
|
|
56
|
+
VerboseOptions,
|
|
57
|
+
} from "./plugin.js";
|
|
58
|
+
|
|
59
|
+
// Types used in BlockRenderer overrides
|
|
64
60
|
export type {
|
|
65
|
-
// ACP SDK
|
|
66
|
-
Agent,
|
|
67
|
-
Client,
|
|
68
|
-
ContentBlock,
|
|
69
|
-
SessionNotification,
|
|
70
|
-
RequestPermissionRequest,
|
|
71
|
-
RequestPermissionResponse,
|
|
72
|
-
// SDK
|
|
73
61
|
BlockKind,
|
|
62
|
+
CommandEntry,
|
|
74
63
|
VerboseConfig,
|
|
75
64
|
BlockRendererOptions,
|
|
76
|
-
PluginCapabilities,
|
|
77
|
-
PluginManifest,
|
|
78
|
-
PluginInitMeta,
|
|
79
65
|
} from "./types.js";
|
|
66
|
+
|
|
67
|
+
// ACP types the plugin needs for prompt content
|
|
68
|
+
export type {
|
|
69
|
+
Agent,
|
|
70
|
+
ContentBlock,
|
|
71
|
+
SessionNotification,
|
|
72
|
+
} from "./types.js";
|
|
73
|
+
|
|
74
|
+
// Error utility
|
|
75
|
+
export { extractErrorMessage } from "./errors.js";
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runChannelPlugin — the SDK entry point for every channel plugin.
|
|
3
|
+
*
|
|
4
|
+
* Handles the full ACP lifecycle: connect to host, validate config, create
|
|
5
|
+
* bot + renderer, start the bot, then await disconnect and stop. The plugin
|
|
6
|
+
* only implements platform-specific transport (sendText, sendBlock, editBlock).
|
|
7
|
+
*
|
|
8
|
+
* ## Usage
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { runChannelPlugin } from "@vibearound/plugin-channel-sdk";
|
|
12
|
+
*
|
|
13
|
+
* runChannelPlugin({
|
|
14
|
+
* name: "vibearound-slack",
|
|
15
|
+
* version: "0.1.0",
|
|
16
|
+
* requiredConfig: ["bot_token", "app_token"],
|
|
17
|
+
* createBot: ({ config, agent, log, cacheDir }) =>
|
|
18
|
+
* new SlackBot({ ... }, agent, log, cacheDir),
|
|
19
|
+
* createRenderer: (bot, log, verbose) =>
|
|
20
|
+
* new SlackRenderer(bot, log, verbose),
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import os from "node:os";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import type { Agent } from "@agentclientprotocol/sdk";
|
|
28
|
+
|
|
29
|
+
import { connectToHost, stripExtPrefix } from "./connection.js";
|
|
30
|
+
import { extractErrorMessage } from "./errors.js";
|
|
31
|
+
import { BlockRenderer } from "./renderer.js";
|
|
32
|
+
import type {
|
|
33
|
+
RequestPermissionRequest,
|
|
34
|
+
RequestPermissionResponse,
|
|
35
|
+
SessionNotification,
|
|
36
|
+
} from "./types.js";
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Public types
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export type ChannelPluginLogger = (level: string, msg: string) => void;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The platform bot — handles IM connectivity and message transport.
|
|
46
|
+
*
|
|
47
|
+
* Plugins implement this interface on their bot class. The SDK calls these
|
|
48
|
+
* methods during the plugin lifecycle.
|
|
49
|
+
*/
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
export interface ChannelBot<TRenderer extends BlockRenderer<any> = BlockRenderer<any>> {
|
|
52
|
+
/** Wire the renderer to receive streaming events. */
|
|
53
|
+
setStreamHandler(handler: TRenderer): void;
|
|
54
|
+
/** Connect to the IM platform and start receiving messages. */
|
|
55
|
+
start(): Promise<void> | void;
|
|
56
|
+
/** Disconnect and clean up. */
|
|
57
|
+
stop(): Promise<void> | void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CreateBotContext {
|
|
61
|
+
config: Record<string, unknown>;
|
|
62
|
+
agent: Agent;
|
|
63
|
+
log: ChannelPluginLogger;
|
|
64
|
+
cacheDir: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface VerboseOptions {
|
|
68
|
+
showThinking: boolean;
|
|
69
|
+
showToolUse: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RunChannelPluginSpec<
|
|
73
|
+
TBot extends ChannelBot<TRenderer>,
|
|
74
|
+
TRenderer extends BlockRenderer<any>,
|
|
75
|
+
> {
|
|
76
|
+
/** Plugin name reported during ACP initialize (e.g. "vibearound-slack"). */
|
|
77
|
+
name: string;
|
|
78
|
+
|
|
79
|
+
/** Plugin version reported during ACP initialize. */
|
|
80
|
+
version: string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Config keys that MUST be present. Plugin fails fast if any are missing.
|
|
84
|
+
*/
|
|
85
|
+
requiredConfig?: string[];
|
|
86
|
+
|
|
87
|
+
/** Factory: build the platform bot. */
|
|
88
|
+
createBot: (ctx: CreateBotContext) => TBot | Promise<TBot>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Factory: build the renderer (extends BlockRenderer).
|
|
92
|
+
* Only implements platform-specific sendText/sendBlock/editBlock.
|
|
93
|
+
*/
|
|
94
|
+
createRenderer: (
|
|
95
|
+
bot: TBot,
|
|
96
|
+
log: ChannelPluginLogger,
|
|
97
|
+
verbose: VerboseOptions,
|
|
98
|
+
) => TRenderer;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Optional hook invoked after bot constructed but before start().
|
|
102
|
+
*/
|
|
103
|
+
afterCreate?: (bot: TBot, log: ChannelPluginLogger) => Promise<void> | void;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Implementation
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Run a channel plugin.
|
|
112
|
+
*
|
|
113
|
+
* Handles the full ACP lifecycle: connect to host, validate config,
|
|
114
|
+
* construct bot + renderer, start the bot, then wait for the host
|
|
115
|
+
* to disconnect before stopping and exiting.
|
|
116
|
+
*/
|
|
117
|
+
export async function runChannelPlugin<
|
|
118
|
+
TBot extends ChannelBot<TRenderer>,
|
|
119
|
+
TRenderer extends BlockRenderer<any>,
|
|
120
|
+
>(spec: RunChannelPluginSpec<TBot, TRenderer>): Promise<void> {
|
|
121
|
+
const prefix = `[${spec.name.replace(/^vibearound-/, "")}-plugin]`;
|
|
122
|
+
const log: ChannelPluginLogger = (level, msg) => {
|
|
123
|
+
process.stderr.write(`${prefix}[${level}] ${msg}\n`);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await runInner(spec, log);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
log("error", `fatal: ${extractErrorMessage(err)}`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runInner<
|
|
135
|
+
TBot extends ChannelBot<TRenderer>,
|
|
136
|
+
TRenderer extends BlockRenderer<any>,
|
|
137
|
+
>(
|
|
138
|
+
spec: RunChannelPluginSpec<TBot, TRenderer>,
|
|
139
|
+
log: ChannelPluginLogger,
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
log("info", "initializing ACP connection...");
|
|
142
|
+
|
|
143
|
+
let renderer: TRenderer | null = null;
|
|
144
|
+
|
|
145
|
+
const { agent, meta, agentInfo, conn } = await connectToHost(
|
|
146
|
+
{ name: spec.name, version: spec.version },
|
|
147
|
+
() => ({
|
|
148
|
+
async sessionUpdate(params: SessionNotification): Promise<void> {
|
|
149
|
+
renderer?.onSessionUpdate(params);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async requestPermission(
|
|
153
|
+
params: RequestPermissionRequest,
|
|
154
|
+
): Promise<RequestPermissionResponse> {
|
|
155
|
+
const first = params.options?.[0];
|
|
156
|
+
if (first) {
|
|
157
|
+
return { outcome: { outcome: "selected", optionId: first.optionId } };
|
|
158
|
+
}
|
|
159
|
+
throw new Error("No permission options provided");
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
async extNotification(
|
|
163
|
+
method: string,
|
|
164
|
+
params: Record<string, unknown>,
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
const chatId = typeof params.chatId === "string" ? params.chatId : undefined;
|
|
167
|
+
switch (stripExtPrefix(method)) {
|
|
168
|
+
case "va/system_text": {
|
|
169
|
+
const text = typeof params.text === "string" ? params.text : "";
|
|
170
|
+
if (chatId && renderer) {
|
|
171
|
+
renderer.onSystemText(chatId, text);
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case "va/agent_ready": {
|
|
176
|
+
const agentName = typeof params.agent === "string" ? params.agent : "unknown";
|
|
177
|
+
const version = typeof params.version === "string" ? params.version : "";
|
|
178
|
+
log("info", `agent_ready: ${agentName} v${version}`);
|
|
179
|
+
if (chatId && renderer) {
|
|
180
|
+
renderer.onAgentReady(chatId, agentName, version);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "va/session_ready": {
|
|
185
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : "";
|
|
186
|
+
log("info", `session_ready: ${sessionId}`);
|
|
187
|
+
if (chatId && renderer) {
|
|
188
|
+
renderer.onSessionReady(chatId, sessionId);
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case "va/command_menu": {
|
|
193
|
+
const systemCommands = Array.isArray(params.systemCommands) ? params.systemCommands : [];
|
|
194
|
+
const agentCommands = Array.isArray(params.agentCommands) ? params.agentCommands : [];
|
|
195
|
+
if (chatId && renderer) {
|
|
196
|
+
renderer.onCommandMenu(chatId, systemCommands, agentCommands);
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
default:
|
|
201
|
+
log("warn", `unhandled ext_notification: ${method}`);
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const config = meta.config;
|
|
208
|
+
|
|
209
|
+
for (const key of spec.requiredConfig ?? []) {
|
|
210
|
+
if (config[key] === undefined || config[key] === null || config[key] === "") {
|
|
211
|
+
throw new Error(`${key} is required in config`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const cacheDir =
|
|
216
|
+
meta.cacheDir ?? path.join(os.homedir(), ".vibearound", ".cache");
|
|
217
|
+
|
|
218
|
+
log(
|
|
219
|
+
"info",
|
|
220
|
+
`initialized, host=${agentInfo.name ?? "unknown"} cacheDir=${cacheDir}`,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const bot = await spec.createBot({ config, agent, log, cacheDir });
|
|
224
|
+
|
|
225
|
+
if (spec.afterCreate) {
|
|
226
|
+
await spec.afterCreate(bot, log);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const verboseRaw = config.verbose as
|
|
230
|
+
| { show_thinking?: boolean; show_tool_use?: boolean }
|
|
231
|
+
| undefined;
|
|
232
|
+
const verbose: VerboseOptions = {
|
|
233
|
+
showThinking: verboseRaw?.show_thinking ?? false,
|
|
234
|
+
showToolUse: verboseRaw?.show_tool_use ?? false,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
renderer = spec.createRenderer(bot, log, verbose);
|
|
238
|
+
bot.setStreamHandler(renderer);
|
|
239
|
+
|
|
240
|
+
await bot.start();
|
|
241
|
+
log("info", "plugin started");
|
|
242
|
+
|
|
243
|
+
await conn.closed;
|
|
244
|
+
log("info", "connection closed, shutting down");
|
|
245
|
+
await bot.stop();
|
|
246
|
+
process.exit(0);
|
|
247
|
+
}
|