@lijian-ui/dsh-im-gateway 0.1.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/LICENSE +21 -0
- package/README.md +183 -0
- package/README.zh-CN.md +207 -0
- package/cordis.patch.yml +24 -0
- package/lib/client.js +1233 -0
- package/lib/index-2mMnMMFx.d.ts +304 -0
- package/lib/index.js +3623 -0
- package/package.json +103 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { Context, Context as Context$1, Service } from "@deepseek-ai/cordis";
|
|
2
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
3
|
+
import * as _deepseek_ai_cosmokit0 from "@deepseek-ai/cosmokit";
|
|
4
|
+
|
|
5
|
+
//#region src/gateway/types.d.ts
|
|
6
|
+
/** An attached image. `data` is raw base64 WITHOUT the `data:` prefix. */
|
|
7
|
+
interface ImImage {
|
|
8
|
+
mediaType: string;
|
|
9
|
+
data: string;
|
|
10
|
+
}
|
|
11
|
+
/** A normalized inbound message coming from any channel. */
|
|
12
|
+
interface ImInboundMessage {
|
|
13
|
+
/** Channel id, e.g. 'dingtalk'. Must match the adapter's `id`. */
|
|
14
|
+
channelId: string;
|
|
15
|
+
/** Channel-specific conversation / chat id. */
|
|
16
|
+
conversationId: string;
|
|
17
|
+
/** End-user id inside the channel. */
|
|
18
|
+
userId: string;
|
|
19
|
+
/** Text body (slash-command prefixes are still present here; the gateway strips them). */
|
|
20
|
+
text: string;
|
|
21
|
+
/** Attached images, if any. */
|
|
22
|
+
images?: ImImage[];
|
|
23
|
+
/** Display name of the sender (group chats) — used to attribute who spoke. */
|
|
24
|
+
senderNick?: string;
|
|
25
|
+
/** Whether the inbound arrived in a group conversation. */
|
|
26
|
+
isGroup?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Runtime status of a channel connection, surfaced to the settings UI. */
|
|
29
|
+
interface ChannelStatus {
|
|
30
|
+
/** Connection state. */
|
|
31
|
+
status: 'online' | 'offline' | 'error';
|
|
32
|
+
/** Present when `status === 'error'`. */
|
|
33
|
+
error?: string;
|
|
34
|
+
/** Epoch ms of the last state change. */
|
|
35
|
+
lastChange: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A channel adapter. Each IM backend (DingTalk / QQ / Weixin) implements this
|
|
39
|
+
* and registers itself via `ctx.imGateway.registerChannel(adapter)`.
|
|
40
|
+
*
|
|
41
|
+
* Lifecycle: the gateway calls `start()` once the core service is active and
|
|
42
|
+
* `stop()` on teardown. The adapter pushes inbound messages back through
|
|
43
|
+
* `ctx.imGateway.handleInbound(message)`.
|
|
44
|
+
*/
|
|
45
|
+
interface ImChannelAdapter {
|
|
46
|
+
/** Stable channel id (e.g. 'dingtalk'). Used as the registry key and in session mapping. */
|
|
47
|
+
readonly id: string;
|
|
48
|
+
/** Human-readable label for logs / settings UI. */
|
|
49
|
+
readonly label: string;
|
|
50
|
+
/** Connect to the backend (open WS / start polling). */
|
|
51
|
+
start(): Promise<void> | void;
|
|
52
|
+
/** Disconnect and release resources. */
|
|
53
|
+
stop(): Promise<void> | void;
|
|
54
|
+
/** Push a text reply to a conversation. */
|
|
55
|
+
sendText(conversationId: string, text: string): Promise<void>;
|
|
56
|
+
/** Push an image reply. Optional — channels without media support may omit. */
|
|
57
|
+
sendImage?(conversationId: string, image: ImImage): Promise<void>;
|
|
58
|
+
/** Whether the adapter is currently connected. */
|
|
59
|
+
isActive(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Optional streaming hook. The gateway calls this with incremental text
|
|
62
|
+
* (throttled by `streamThrottleMs`) so card-based channels (DingTalk) can
|
|
63
|
+
* update an AI card instead of sending discrete messages. Channels without
|
|
64
|
+
* this hook receive the full reply via `sendText` on `turn/end`.
|
|
65
|
+
*/
|
|
66
|
+
updateCard?(conversationId: string, partialText: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Optional AI-card streaming lifecycle — the richer sibling of `updateCard`.
|
|
69
|
+
* A channel that implements these three methods gets a full streaming card:
|
|
70
|
+
* beginStream (create + deliver the card, once per reply cycle)
|
|
71
|
+
* streamText (incremental content updates, throttled by the gateway)
|
|
72
|
+
* endStream (finalize FINISHED; must be called even with no text)
|
|
73
|
+
* Channels without them fall back to a single `sendText` at `turn/end`.
|
|
74
|
+
*/
|
|
75
|
+
beginStream?(conversationId: string): Promise<void>;
|
|
76
|
+
streamText?(conversationId: string, text: string, finished?: boolean): Promise<void>;
|
|
77
|
+
endStream?(conversationId: string, text: string): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Optional pre-filter. Called by the gateway for every inbound message
|
|
80
|
+
* BEFORE it is forwarded to the agent. Return `true` to suppress forwarding
|
|
81
|
+
* (e.g. the message was a slash command handled locally).
|
|
82
|
+
*/
|
|
83
|
+
onInbound?(message: ImInboundMessage): boolean | Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Optional status sink. The gateway assigns this on registration; the adapter
|
|
86
|
+
* must call it whenever its connection state changes (connected / closed /
|
|
87
|
+
* error) so the settings page can render online / offline / error badges.
|
|
88
|
+
*/
|
|
89
|
+
statusListener?(status: ChannelStatus): void;
|
|
90
|
+
}
|
|
91
|
+
/** DingTalk channel credentials (per instance). */
|
|
92
|
+
interface DingtalkChannelConfig {
|
|
93
|
+
clientId: string;
|
|
94
|
+
clientSecret: string;
|
|
95
|
+
callbackBaseUrl: string;
|
|
96
|
+
}
|
|
97
|
+
/** QQ channel credentials (per instance). */
|
|
98
|
+
interface QqChannelConfig {
|
|
99
|
+
appId: string;
|
|
100
|
+
clientSecret: string;
|
|
101
|
+
botAppId: string;
|
|
102
|
+
}
|
|
103
|
+
/** 微信(iLink) channel credentials (per instance). */
|
|
104
|
+
interface WeixinChannelConfig {
|
|
105
|
+
/** iLink bot token — obtained via QR scan login (weixin-login.ts). */
|
|
106
|
+
token: string;
|
|
107
|
+
/** iLink bot id — obtained via QR scan login (weixin-login.ts). */
|
|
108
|
+
botId: string;
|
|
109
|
+
/** API base URL (defaults to https://ilinkai.weixin.qq.com). */
|
|
110
|
+
baseUrl: string;
|
|
111
|
+
/** CDN base URL for media download/upload (defaults to https://cdn.ilinkai.weixin.qq.com). */
|
|
112
|
+
cdnBaseUrl: string;
|
|
113
|
+
pollIntervalMs: number;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* One IM robot instance. The same channel TYPE may appear many times — e.g.
|
|
117
|
+
* two DingTalk bots with different credentials — each with its own unique
|
|
118
|
+
* `id` (e.g. `dingtalk-main`, `qq-7f3a`). `id` MUST NOT contain ':' because
|
|
119
|
+
* the gateway builds `convKey = channelId:conversationId`.
|
|
120
|
+
*/
|
|
121
|
+
type ChannelType = 'dingtalk' | 'qq' | 'weixin';
|
|
122
|
+
interface ImChannelInstance {
|
|
123
|
+
/** Unique instance id, `<type>-<slug>` — used as the adapter registry key. */
|
|
124
|
+
id: string;
|
|
125
|
+
/** Channel type (drives which adapter class + which credential fields). */
|
|
126
|
+
type: ChannelType;
|
|
127
|
+
/** Human-readable display name (e.g. '主机器人'). */
|
|
128
|
+
name: string;
|
|
129
|
+
enabled: boolean;
|
|
130
|
+
/** Credential fields for this instance (shape depends on `type`). */
|
|
131
|
+
config: DingtalkChannelConfig & Partial<QqChannelConfig> & Partial<WeixinChannelConfig>;
|
|
132
|
+
}
|
|
133
|
+
interface ImGatewayConfig {
|
|
134
|
+
/** Working directory passed to host sessions created for IM conversations. */
|
|
135
|
+
cwd: string;
|
|
136
|
+
/** Throttle (ms) for incremental `updateCard` pushes. Ignored when a channel has no `updateCard`. */
|
|
137
|
+
streamThrottleMs: number;
|
|
138
|
+
/** Enable built-in slash commands (/help, /reset, /clear). */
|
|
139
|
+
slashCommands: boolean;
|
|
140
|
+
/** All channel instances. Only `enabled` ones connect out. Empty by default. */
|
|
141
|
+
channels: ImChannelInstance[];
|
|
142
|
+
}
|
|
143
|
+
/** Public surface of the gateway service, exposed as `ctx.imGateway`. */
|
|
144
|
+
interface ImGateway {
|
|
145
|
+
registerChannel(adapter: ImChannelAdapter): void;
|
|
146
|
+
getChannel(id: string): ImChannelAdapter | undefined;
|
|
147
|
+
listChannels(): string[];
|
|
148
|
+
/** Entry point channel adapters call for every inbound user message. */
|
|
149
|
+
handleInbound(message: ImInboundMessage): Promise<void>;
|
|
150
|
+
/** Snapshot of every channel's runtime status (for the settings UI). */
|
|
151
|
+
getChannelStatuses(): Record<string, ChannelStatus>;
|
|
152
|
+
}
|
|
153
|
+
sideEffect();
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/gateway/im-gateway.d.ts
|
|
157
|
+
declare const Config: Schema<Schemastery.ObjectS<{
|
|
158
|
+
cwd: Schema<string, string>;
|
|
159
|
+
streamThrottleMs: Schema<number, number>;
|
|
160
|
+
slashCommands: Schema<boolean, boolean>;
|
|
161
|
+
channels: Schema<({
|
|
162
|
+
id?: string | null | undefined;
|
|
163
|
+
type?: "dingtalk" | "qq" | "weixin" | null | undefined;
|
|
164
|
+
name?: string | null | undefined;
|
|
165
|
+
enabled?: boolean | null | undefined;
|
|
166
|
+
config?: ({
|
|
167
|
+
clientId?: string | null | undefined;
|
|
168
|
+
clientSecret?: string | null | undefined;
|
|
169
|
+
callbackBaseUrl?: string | null | undefined;
|
|
170
|
+
appId?: string | null | undefined;
|
|
171
|
+
botAppId?: string | null | undefined;
|
|
172
|
+
baseUrl?: string | null | undefined;
|
|
173
|
+
token?: string | null | undefined;
|
|
174
|
+
botId?: string | null | undefined;
|
|
175
|
+
cdnBaseUrl?: string | null | undefined;
|
|
176
|
+
pollIntervalMs?: number | null | undefined;
|
|
177
|
+
} & _deepseek_ai_cosmokit0.Dict) | null | undefined;
|
|
178
|
+
} & _deepseek_ai_cosmokit0.Dict)[], Schemastery.ObjectT<{
|
|
179
|
+
id: Schema<string, string>;
|
|
180
|
+
type: Schema<"dingtalk" | "qq" | "weixin", "dingtalk" | "qq" | "weixin">;
|
|
181
|
+
name: Schema<string, string>;
|
|
182
|
+
enabled: Schema<boolean, boolean>;
|
|
183
|
+
config: Schema<Schemastery.ObjectS<{
|
|
184
|
+
clientId: Schema<string, string>;
|
|
185
|
+
clientSecret: Schema<string, string>;
|
|
186
|
+
callbackBaseUrl: Schema<string, string>;
|
|
187
|
+
appId: Schema<string, string>;
|
|
188
|
+
botAppId: Schema<string, string>;
|
|
189
|
+
baseUrl: Schema<string, string>;
|
|
190
|
+
token: Schema<string, string>;
|
|
191
|
+
botId: Schema<string, string>;
|
|
192
|
+
cdnBaseUrl: Schema<string, string>;
|
|
193
|
+
pollIntervalMs: Schema<number, number>;
|
|
194
|
+
}>, Schemastery.ObjectT<{
|
|
195
|
+
clientId: Schema<string, string>;
|
|
196
|
+
clientSecret: Schema<string, string>;
|
|
197
|
+
callbackBaseUrl: Schema<string, string>;
|
|
198
|
+
appId: Schema<string, string>;
|
|
199
|
+
botAppId: Schema<string, string>;
|
|
200
|
+
baseUrl: Schema<string, string>;
|
|
201
|
+
token: Schema<string, string>;
|
|
202
|
+
botId: Schema<string, string>;
|
|
203
|
+
cdnBaseUrl: Schema<string, string>;
|
|
204
|
+
pollIntervalMs: Schema<number, number>;
|
|
205
|
+
}>>;
|
|
206
|
+
}>[]>;
|
|
207
|
+
}>, Schemastery.ObjectT<{
|
|
208
|
+
cwd: Schema<string, string>;
|
|
209
|
+
streamThrottleMs: Schema<number, number>;
|
|
210
|
+
slashCommands: Schema<boolean, boolean>;
|
|
211
|
+
channels: Schema<({
|
|
212
|
+
id?: string | null | undefined;
|
|
213
|
+
type?: "dingtalk" | "qq" | "weixin" | null | undefined;
|
|
214
|
+
name?: string | null | undefined;
|
|
215
|
+
enabled?: boolean | null | undefined;
|
|
216
|
+
config?: ({
|
|
217
|
+
clientId?: string | null | undefined;
|
|
218
|
+
clientSecret?: string | null | undefined;
|
|
219
|
+
callbackBaseUrl?: string | null | undefined;
|
|
220
|
+
appId?: string | null | undefined;
|
|
221
|
+
botAppId?: string | null | undefined;
|
|
222
|
+
baseUrl?: string | null | undefined;
|
|
223
|
+
token?: string | null | undefined;
|
|
224
|
+
botId?: string | null | undefined;
|
|
225
|
+
cdnBaseUrl?: string | null | undefined;
|
|
226
|
+
pollIntervalMs?: number | null | undefined;
|
|
227
|
+
} & _deepseek_ai_cosmokit0.Dict) | null | undefined;
|
|
228
|
+
} & _deepseek_ai_cosmokit0.Dict)[], Schemastery.ObjectT<{
|
|
229
|
+
id: Schema<string, string>;
|
|
230
|
+
type: Schema<"dingtalk" | "qq" | "weixin", "dingtalk" | "qq" | "weixin">;
|
|
231
|
+
name: Schema<string, string>;
|
|
232
|
+
enabled: Schema<boolean, boolean>;
|
|
233
|
+
config: Schema<Schemastery.ObjectS<{
|
|
234
|
+
clientId: Schema<string, string>;
|
|
235
|
+
clientSecret: Schema<string, string>;
|
|
236
|
+
callbackBaseUrl: Schema<string, string>;
|
|
237
|
+
appId: Schema<string, string>;
|
|
238
|
+
botAppId: Schema<string, string>;
|
|
239
|
+
baseUrl: Schema<string, string>;
|
|
240
|
+
token: Schema<string, string>;
|
|
241
|
+
botId: Schema<string, string>;
|
|
242
|
+
cdnBaseUrl: Schema<string, string>;
|
|
243
|
+
pollIntervalMs: Schema<number, number>;
|
|
244
|
+
}>, Schemastery.ObjectT<{
|
|
245
|
+
clientId: Schema<string, string>;
|
|
246
|
+
clientSecret: Schema<string, string>;
|
|
247
|
+
callbackBaseUrl: Schema<string, string>;
|
|
248
|
+
appId: Schema<string, string>;
|
|
249
|
+
botAppId: Schema<string, string>;
|
|
250
|
+
baseUrl: Schema<string, string>;
|
|
251
|
+
token: Schema<string, string>;
|
|
252
|
+
botId: Schema<string, string>;
|
|
253
|
+
cdnBaseUrl: Schema<string, string>;
|
|
254
|
+
pollIntervalMs: Schema<number, number>;
|
|
255
|
+
}>>;
|
|
256
|
+
}>[]>;
|
|
257
|
+
}>>;
|
|
258
|
+
/**
|
|
259
|
+
* Core IM gateway service.
|
|
260
|
+
*
|
|
261
|
+
* Responsibilities (ported from pi-desk-top/src/main/im/im-gateway.ts):
|
|
262
|
+
* - channel registry (`registerChannel`)
|
|
263
|
+
* - conversation → host session mapping
|
|
264
|
+
* - per-conversation serial queue (one agent turn at a time)
|
|
265
|
+
* - slash-command handling (/help, /reset, /clear)
|
|
266
|
+
* - agent-event → channel reply routing (streaming, tool notifications, flush)
|
|
267
|
+
*
|
|
268
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
269
|
+
* HOST API CONTRACT (verified against deepseek-harness source):
|
|
270
|
+
* - Sessions: `ctx.sessions` is the `SessionStore` (from @deepseek-ai/dsh-session).
|
|
271
|
+
* `create(id, { meta: { cwd } })` builds an event-sourced Session.
|
|
272
|
+
* The Session object has NO `.prompt()` / `.on()` — it is a log.
|
|
273
|
+
* - Agents: `ctx.agents` is the `AgentRegistry` (from @deepseek-ai/dsh-agent).
|
|
274
|
+
* `get(sessionId)` → Agent | undefined; `createAgent(ownerCtx, opts)`
|
|
275
|
+
* starts the agent loop on a session. Drive a turn with
|
|
276
|
+
* `agent.followup(userMessage)` (see host/apiproxy/src/api-proxy.ts:2461).
|
|
277
|
+
* - Events: subscribe at the CONTEXT level: `ctx.on('session/event', (session, event) => …)`.
|
|
278
|
+
* Typed events (packages/core/session/src/types.ts):
|
|
279
|
+
* 'turn/start' { turn }
|
|
280
|
+
* 'assistant/chunk' { turn, step, chunk: StreamChunk } // chunk.type==='text-delta' → text
|
|
281
|
+
* 'assistant/message' { turn, step, message: AssistantMessage }
|
|
282
|
+
* 'tool/call' { turn, step, callId, name, arguments }
|
|
283
|
+
* 'turn/end' { turn, reason }
|
|
284
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
285
|
+
*/
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/index.d.ts
|
|
288
|
+
/**
|
|
289
|
+
* Plugin-level dependency declaration: the bundle waits for these host
|
|
290
|
+
* services before apply runs (mirrors the official dsh-skill-viewer host:
|
|
291
|
+
* `export const inject = ["typert", ...]`). `imGateway` (our own core
|
|
292
|
+
* service) is created inside apply and waited on via ctx.inject below.
|
|
293
|
+
*/
|
|
294
|
+
declare const inject: string[];
|
|
295
|
+
/**
|
|
296
|
+
* Single-entry IM gateway bundle. One plugin (`@lijian-ui/dsh-im-gateway`) provides the
|
|
297
|
+
* core gateway service AND every channel (DingTalk / QQ / 个人微信) as
|
|
298
|
+
* INSTANCES: `config.channels` is an array, and the same channel type may
|
|
299
|
+
* appear multiple times (multi-bot support, mirroring pi-desk-top). Only
|
|
300
|
+
* `enabled` instances connect out.
|
|
301
|
+
*/
|
|
302
|
+
declare function apply(ctx: Context$1, config: ImGatewayConfig): void;
|
|
303
|
+
//#endregion
|
|
304
|
+
export { ChannelStatus, ChannelType, Config, type Context, DingtalkChannelConfig, ImChannelAdapter, ImChannelInstance, ImGateway, ImGatewayConfig, ImImage, ImInboundMessage, QqChannelConfig, WeixinChannelConfig, apply, inject };
|