@vibearound/plugin-channel-sdk 0.3.0 → 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/index.d.ts +27 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -45
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +80 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/{run-plugin.js → plugin.js} +43 -44
- 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 +114 -90
- 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/index.ts +41 -60
- package/src/{run-plugin.ts → plugin.ts} +70 -72
- package/src/renderer.ts +142 -97
- package/src/types.ts +24 -0
- package/dist/run-plugin.d.ts +0 -95
- package/dist/run-plugin.d.ts.map +0 -1
- package/dist/run-plugin.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,94 +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
|
+
// ---------------------------------------------------------------------------
|
|
59
43
|
|
|
60
|
-
//
|
|
61
|
-
export {
|
|
44
|
+
// Entry point
|
|
45
|
+
export { runChannelPlugin } from "./plugin.js";
|
|
62
46
|
|
|
63
|
-
//
|
|
64
|
-
export {
|
|
47
|
+
// Base class for stream rendering
|
|
48
|
+
export { BlockRenderer } from "./renderer.js";
|
|
49
|
+
|
|
50
|
+
// Interfaces the plugin implements
|
|
65
51
|
export type {
|
|
66
52
|
ChannelBot,
|
|
67
53
|
ChannelPluginLogger,
|
|
68
|
-
ChannelStreamHandler,
|
|
69
54
|
CreateBotContext,
|
|
70
55
|
RunChannelPluginSpec,
|
|
71
56
|
VerboseOptions,
|
|
72
|
-
} from "./
|
|
73
|
-
|
|
74
|
-
// Block renderer
|
|
75
|
-
export { BlockRenderer } from "./renderer.js";
|
|
57
|
+
} from "./plugin.js";
|
|
76
58
|
|
|
59
|
+
// Types used in BlockRenderer overrides
|
|
60
|
+
export type {
|
|
61
|
+
BlockKind,
|
|
62
|
+
CommandEntry,
|
|
63
|
+
VerboseConfig,
|
|
64
|
+
BlockRendererOptions,
|
|
65
|
+
} from "./types.js";
|
|
77
66
|
|
|
78
|
-
//
|
|
67
|
+
// ACP types the plugin needs for prompt content
|
|
79
68
|
export type {
|
|
80
|
-
// ACP SDK
|
|
81
69
|
Agent,
|
|
82
|
-
Client,
|
|
83
70
|
ContentBlock,
|
|
84
71
|
SessionNotification,
|
|
85
|
-
RequestPermissionRequest,
|
|
86
|
-
RequestPermissionResponse,
|
|
87
|
-
// SDK
|
|
88
|
-
BlockKind,
|
|
89
|
-
VerboseConfig,
|
|
90
|
-
BlockRendererOptions,
|
|
91
|
-
PluginCapabilities,
|
|
92
|
-
PluginManifest,
|
|
93
|
-
PluginInitMeta,
|
|
94
72
|
} from "./types.js";
|
|
73
|
+
|
|
74
|
+
// Error utility
|
|
75
|
+
export { extractErrorMessage } from "./errors.js";
|
|
@@ -1,34 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* runChannelPlugin —
|
|
2
|
+
* runChannelPlugin — the SDK entry point for every channel plugin.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* and extNotification handlers, create the stream handler, start the bot,
|
|
8
|
-
* wait for disconnect, stop. This helper absorbs that boilerplate so each
|
|
9
|
-
* plugin's `main.ts` reduces to ~20 lines — a factory for the bot and a
|
|
10
|
-
* factory for the stream handler.
|
|
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).
|
|
11
7
|
*
|
|
12
8
|
* ## Usage
|
|
13
9
|
*
|
|
14
10
|
* ```ts
|
|
15
11
|
* import { runChannelPlugin } from "@vibearound/plugin-channel-sdk";
|
|
16
|
-
* import { SlackBot } from "./bot.js";
|
|
17
|
-
* import { AgentStreamHandler } from "./agent-stream.js";
|
|
18
12
|
*
|
|
19
13
|
* runChannelPlugin({
|
|
20
14
|
* name: "vibearound-slack",
|
|
21
15
|
* version: "0.1.0",
|
|
22
16
|
* requiredConfig: ["bot_token", "app_token"],
|
|
23
17
|
* createBot: ({ config, agent, log, cacheDir }) =>
|
|
24
|
-
* new SlackBot(
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* log,
|
|
28
|
-
* cacheDir,
|
|
29
|
-
* ),
|
|
30
|
-
* createStreamHandler: (bot, log, verbose) =>
|
|
31
|
-
* new AgentStreamHandler(bot, log, verbose),
|
|
18
|
+
* new SlackBot({ ... }, agent, log, cacheDir),
|
|
19
|
+
* createRenderer: (bot, log, verbose) =>
|
|
20
|
+
* new SlackRenderer(bot, log, verbose),
|
|
32
21
|
* });
|
|
33
22
|
* ```
|
|
34
23
|
*/
|
|
@@ -37,8 +26,9 @@ import os from "node:os";
|
|
|
37
26
|
import path from "node:path";
|
|
38
27
|
import type { Agent } from "@agentclientprotocol/sdk";
|
|
39
28
|
|
|
40
|
-
import { connectToHost,
|
|
29
|
+
import { connectToHost, stripExtPrefix } from "./connection.js";
|
|
41
30
|
import { extractErrorMessage } from "./errors.js";
|
|
31
|
+
import { BlockRenderer } from "./renderer.js";
|
|
42
32
|
import type {
|
|
43
33
|
RequestPermissionRequest,
|
|
44
34
|
RequestPermissionResponse,
|
|
@@ -51,16 +41,19 @@ import type {
|
|
|
51
41
|
|
|
52
42
|
export type ChannelPluginLogger = (level: string, msg: string) => void;
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export interface ChannelBot<
|
|
62
|
-
|
|
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. */
|
|
63
55
|
start(): Promise<void> | void;
|
|
56
|
+
/** Disconnect and clean up. */
|
|
64
57
|
stop(): Promise<void> | void;
|
|
65
58
|
}
|
|
66
59
|
|
|
@@ -77,8 +70,8 @@ export interface VerboseOptions {
|
|
|
77
70
|
}
|
|
78
71
|
|
|
79
72
|
export interface RunChannelPluginSpec<
|
|
80
|
-
TBot extends ChannelBot<
|
|
81
|
-
|
|
73
|
+
TBot extends ChannelBot<TRenderer>,
|
|
74
|
+
TRenderer extends BlockRenderer<any>,
|
|
82
75
|
> {
|
|
83
76
|
/** Plugin name reported during ACP initialize (e.g. "vibearound-slack"). */
|
|
84
77
|
name: string;
|
|
@@ -87,30 +80,25 @@ export interface RunChannelPluginSpec<
|
|
|
87
80
|
version: string;
|
|
88
81
|
|
|
89
82
|
/**
|
|
90
|
-
* Config keys that MUST be present
|
|
91
|
-
* fails with a clear error if any are missing. Keep to primitives
|
|
92
|
-
* (strings/booleans); deeper validation belongs in the bot constructor.
|
|
83
|
+
* Config keys that MUST be present. Plugin fails fast if any are missing.
|
|
93
84
|
*/
|
|
94
85
|
requiredConfig?: string[];
|
|
95
86
|
|
|
96
|
-
/** Factory: build the platform bot
|
|
87
|
+
/** Factory: build the platform bot. */
|
|
97
88
|
createBot: (ctx: CreateBotContext) => TBot | Promise<TBot>;
|
|
98
89
|
|
|
99
90
|
/**
|
|
100
|
-
* Factory: build the
|
|
101
|
-
*
|
|
102
|
-
* bot is started.
|
|
91
|
+
* Factory: build the renderer (extends BlockRenderer).
|
|
92
|
+
* Only implements platform-specific sendText/sendBlock/editBlock.
|
|
103
93
|
*/
|
|
104
|
-
|
|
94
|
+
createRenderer: (
|
|
105
95
|
bot: TBot,
|
|
106
96
|
log: ChannelPluginLogger,
|
|
107
97
|
verbose: VerboseOptions,
|
|
108
|
-
) =>
|
|
98
|
+
) => TRenderer;
|
|
109
99
|
|
|
110
100
|
/**
|
|
111
|
-
* Optional hook invoked after
|
|
112
|
-
* `start()` is called. Use this for one-off initialization that needs
|
|
113
|
-
* to log diagnostic info (e.g. Telegram's `probe()`).
|
|
101
|
+
* Optional hook invoked after bot constructed but before start().
|
|
114
102
|
*/
|
|
115
103
|
afterCreate?: (bot: TBot, log: ChannelPluginLogger) => Promise<void> | void;
|
|
116
104
|
}
|
|
@@ -120,18 +108,16 @@ export interface RunChannelPluginSpec<
|
|
|
120
108
|
// ---------------------------------------------------------------------------
|
|
121
109
|
|
|
122
110
|
/**
|
|
123
|
-
* Run a channel plugin
|
|
124
|
-
*
|
|
125
|
-
* Performs the ACP initialize handshake, validates required config,
|
|
126
|
-
* constructs the bot + stream handler, starts the bot, waits for the host
|
|
127
|
-
* connection to close, then stops the bot and exits the process.
|
|
111
|
+
* Run a channel plugin.
|
|
128
112
|
*
|
|
129
|
-
*
|
|
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.
|
|
130
116
|
*/
|
|
131
117
|
export async function runChannelPlugin<
|
|
132
|
-
TBot extends ChannelBot<
|
|
133
|
-
|
|
134
|
-
>(spec: RunChannelPluginSpec<TBot,
|
|
118
|
+
TBot extends ChannelBot<TRenderer>,
|
|
119
|
+
TRenderer extends BlockRenderer<any>,
|
|
120
|
+
>(spec: RunChannelPluginSpec<TBot, TRenderer>): Promise<void> {
|
|
135
121
|
const prefix = `[${spec.name.replace(/^vibearound-/, "")}-plugin]`;
|
|
136
122
|
const log: ChannelPluginLogger = (level, msg) => {
|
|
137
123
|
process.stderr.write(`${prefix}[${level}] ${msg}\n`);
|
|
@@ -146,21 +132,21 @@ export async function runChannelPlugin<
|
|
|
146
132
|
}
|
|
147
133
|
|
|
148
134
|
async function runInner<
|
|
149
|
-
TBot extends ChannelBot<
|
|
150
|
-
|
|
135
|
+
TBot extends ChannelBot<TRenderer>,
|
|
136
|
+
TRenderer extends BlockRenderer<any>,
|
|
151
137
|
>(
|
|
152
|
-
spec: RunChannelPluginSpec<TBot,
|
|
138
|
+
spec: RunChannelPluginSpec<TBot, TRenderer>,
|
|
153
139
|
log: ChannelPluginLogger,
|
|
154
140
|
): Promise<void> {
|
|
155
141
|
log("info", "initializing ACP connection...");
|
|
156
142
|
|
|
157
|
-
let
|
|
143
|
+
let renderer: TRenderer | null = null;
|
|
158
144
|
|
|
159
145
|
const { agent, meta, agentInfo, conn } = await connectToHost(
|
|
160
146
|
{ name: spec.name, version: spec.version },
|
|
161
147
|
() => ({
|
|
162
148
|
async sessionUpdate(params: SessionNotification): Promise<void> {
|
|
163
|
-
|
|
149
|
+
renderer?.onSessionUpdate(params);
|
|
164
150
|
},
|
|
165
151
|
|
|
166
152
|
async requestPermission(
|
|
@@ -177,23 +163,38 @@ async function runInner<
|
|
|
177
163
|
method: string,
|
|
178
164
|
params: Record<string, unknown>,
|
|
179
165
|
): Promise<void> {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
}
|
|
184
173
|
break;
|
|
185
174
|
}
|
|
186
|
-
case "
|
|
187
|
-
const agentName = params.agent
|
|
188
|
-
const version = params.version
|
|
175
|
+
case "va/agent_ready": {
|
|
176
|
+
const agentName = typeof params.agent === "string" ? params.agent : "unknown";
|
|
177
|
+
const version = typeof params.version === "string" ? params.version : "";
|
|
189
178
|
log("info", `agent_ready: ${agentName} v${version}`);
|
|
190
|
-
|
|
179
|
+
if (chatId && renderer) {
|
|
180
|
+
renderer.onAgentReady(chatId, agentName, version);
|
|
181
|
+
}
|
|
191
182
|
break;
|
|
192
183
|
}
|
|
193
|
-
case "
|
|
194
|
-
const sessionId = params.sessionId
|
|
184
|
+
case "va/session_ready": {
|
|
185
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : "";
|
|
195
186
|
log("info", `session_ready: ${sessionId}`);
|
|
196
|
-
|
|
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
|
+
}
|
|
197
198
|
break;
|
|
198
199
|
}
|
|
199
200
|
default:
|
|
@@ -205,9 +206,6 @@ async function runInner<
|
|
|
205
206
|
|
|
206
207
|
const config = meta.config;
|
|
207
208
|
|
|
208
|
-
// Validate required config keys up front so a misconfigured plugin fails
|
|
209
|
-
// with a clear error instead of some downstream "undefined is not a
|
|
210
|
-
// string" crash in the bot constructor.
|
|
211
209
|
for (const key of spec.requiredConfig ?? []) {
|
|
212
210
|
if (config[key] === undefined || config[key] === null || config[key] === "") {
|
|
213
211
|
throw new Error(`${key} is required in config`);
|
|
@@ -236,8 +234,8 @@ async function runInner<
|
|
|
236
234
|
showToolUse: verboseRaw?.show_tool_use ?? false,
|
|
237
235
|
};
|
|
238
236
|
|
|
239
|
-
|
|
240
|
-
bot.setStreamHandler(
|
|
237
|
+
renderer = spec.createRenderer(bot, log, verbose);
|
|
238
|
+
bot.setStreamHandler(renderer);
|
|
241
239
|
|
|
242
240
|
await bot.start();
|
|
243
241
|
log("info", "plugin started");
|