@krischoichoi/channel-qq 0.6.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.
Files changed (54) hide show
  1. package/README.md +93 -0
  2. package/lib/adapter.d.ts +76 -0
  3. package/lib/adapter.d.ts.map +1 -0
  4. package/lib/adapter.js +299 -0
  5. package/lib/adapter.js.map +1 -0
  6. package/lib/config.d.ts +62 -0
  7. package/lib/config.d.ts.map +1 -0
  8. package/lib/config.js +43 -0
  9. package/lib/config.js.map +1 -0
  10. package/lib/definition.d.ts +75 -0
  11. package/lib/definition.d.ts.map +1 -0
  12. package/lib/definition.js +232 -0
  13. package/lib/definition.js.map +1 -0
  14. package/lib/image-hydrator.d.ts +13 -0
  15. package/lib/image-hydrator.d.ts.map +1 -0
  16. package/lib/image-hydrator.js +13 -0
  17. package/lib/image-hydrator.js.map +1 -0
  18. package/lib/inbound.d.ts +49 -0
  19. package/lib/inbound.d.ts.map +1 -0
  20. package/lib/inbound.js +98 -0
  21. package/lib/inbound.js.map +1 -0
  22. package/lib/index.d.ts +53 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +113 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/interaction-mapper.d.ts +43 -0
  27. package/lib/interaction-mapper.d.ts.map +1 -0
  28. package/lib/interaction-mapper.js +91 -0
  29. package/lib/interaction-mapper.js.map +1 -0
  30. package/lib/manifest.d.ts +22 -0
  31. package/lib/manifest.d.ts.map +1 -0
  32. package/lib/manifest.js +35 -0
  33. package/lib/manifest.js.map +1 -0
  34. package/lib/mapper.d.ts +38 -0
  35. package/lib/mapper.d.ts.map +1 -0
  36. package/lib/mapper.js +109 -0
  37. package/lib/mapper.js.map +1 -0
  38. package/lib/media-hydrator.d.ts +61 -0
  39. package/lib/media-hydrator.d.ts.map +1 -0
  40. package/lib/media-hydrator.js +107 -0
  41. package/lib/media-hydrator.js.map +1 -0
  42. package/lib/outbound.d.ts +41 -0
  43. package/lib/outbound.d.ts.map +1 -0
  44. package/lib/outbound.js +111 -0
  45. package/lib/outbound.js.map +1 -0
  46. package/lib/sdk-client.d.ts +247 -0
  47. package/lib/sdk-client.d.ts.map +1 -0
  48. package/lib/sdk-client.js +312 -0
  49. package/lib/sdk-client.js.map +1 -0
  50. package/lib/streaming-reply.d.ts +19 -0
  51. package/lib/streaming-reply.d.ts.map +1 -0
  52. package/lib/streaming-reply.js +26 -0
  53. package/lib/streaming-reply.js.map +1 -0
  54. package/package.json +46 -0
@@ -0,0 +1,312 @@
1
+ /**
2
+ * DSH SDK port — the thin seam between the QQ adapter and the Tencent SDK.
3
+ *
4
+ * The adapter never touches `QQBot` directly; everything flows through
5
+ * `QQSdkClient`. Production wraps the real `QQBot` (`TencentQQSdkClient`);
6
+ * tests inject a `FakeQQSdkClient` so the entire adapter contract suite runs
7
+ * fully offline (no network, no real credentials).
8
+ */
9
+ import { MediaFileType, QQBot, } from '@tencent-connect/qqbot-nodejs';
10
+ import { ChannelSendError } from '@krischoichoi/channel-core';
11
+ /**
12
+ * QQ Gateway intents the adapter actually consumes today (minimal-intent
13
+ * principle): `GROUP_AND_C2C` covers the C2C/group message events this adapter
14
+ * processes inbound, and `INTERACTION` covers the button `INTERACTION_CREATE`
15
+ * callbacks that drive `interaction.received`. The SDK defaults to
16
+ * `FULL_INTENTS` (guilids + members + guild messages + dm + group/c2c +
17
+ * interaction); requesting only what we use avoids `4914 INSUFFICIENT_INTENTS`
18
+ * / `4915 DISALLOWED_INTENTS` gateway rejections for privileges the bridge
19
+ * never exercises, and keeps the permission surface minimal. Bit values are
20
+ * verified from the pinned SDK (`dist/protocol/gateway/constants.js`):
21
+ * `GROUP_AND_C2C = 1 << 25`, `INTERACTION = 1 << 26`.
22
+ */
23
+ export const QQ_MINIMAL_INTENTS = (1 << 25) | (1 << 26); // GROUP_AND_C2C | INTERACTION
24
+ /** QQ inline-keyboard callback action (emits `INTERACTION_CREATE`). */
25
+ export const QQ_BUTTON_ACTION_TYPE = 1;
26
+ /** QQ inline-keyboard permission: any user may press. */
27
+ export const QQ_BUTTON_PERMISSION_TYPE = 2;
28
+ /** QQ button render style for the `primary` DSH action style. */
29
+ export const QQ_BUTTON_STYLE_PRIMARY = 1;
30
+ /** QQ button render style default. */
31
+ export const QQ_BUTTON_STYLE_DEFAULT = 0;
32
+ /** Bridge a DSH `ChannelLogger` (variadic) to the SDK `Logger` shape. */
33
+ export function adaptLogger(logger) {
34
+ return {
35
+ info: (msg, meta) => logger.info(msg, meta),
36
+ error: (msg, meta) => logger.error(msg, meta),
37
+ warn: (msg, meta) => logger.warn(msg, meta),
38
+ debug: (msg, meta) => logger.debug(msg, meta),
39
+ };
40
+ }
41
+ /** Production `QQSdkClient`: wraps a real `QQBot` built from config. */
42
+ export class TencentQQSdkClient {
43
+ bot;
44
+ constructor(config, logger, appSecret) {
45
+ this.bot = new QQBot({
46
+ appId: config.appId,
47
+ appSecret,
48
+ accountId: config.accountId,
49
+ markdownSupport: config.markdownSupport,
50
+ transport: 'websocket',
51
+ tokenPrefetch: 'sync',
52
+ // Minimal intent mask (GROUP_AND_C2C | INTERACTION) — never rely on the
53
+ // SDK `FULL_INTENTS` default (skill §6.2, P1). See QQ_MINIMAL_INTENTS.
54
+ intents: QQ_MINIMAL_INTENTS,
55
+ logger: adaptLogger(logger),
56
+ });
57
+ }
58
+ onReady(handler) {
59
+ this.bot.on('ready', () => handler());
60
+ }
61
+ onResumed(handler) {
62
+ this.bot.on('resumed', () => handler());
63
+ }
64
+ onError(handler) {
65
+ this.bot.on('error', (error) => handler(error));
66
+ }
67
+ onMessage(handler) {
68
+ this.bot.on('message', (_ctx, message) => handler(message));
69
+ }
70
+ onInteraction(handler) {
71
+ // The SDK dispatches the raw `InteractionEvent` as the second argument;
72
+ // the handler only sees the port slice the adapter needs.
73
+ this.bot.on('interaction', (_ctx, event) => handler(event));
74
+ }
75
+ start(signal) {
76
+ return this.bot.start(signal);
77
+ }
78
+ stop() {
79
+ this.bot.stop();
80
+ }
81
+ sendText(target, text) {
82
+ return this.bot.sendText(target, text);
83
+ }
84
+ sendMarkdownWithKeyboard(target, text, keyboard) {
85
+ // New QQ interactive messages are explicit Markdown messages. Do not use
86
+ // sendTextWithKeyboard(): that helper follows `markdownSupport` and emits
87
+ // msg_type=0 when the legacy config flag is false, in which case current
88
+ // QQ clients do not render the keyboard.
89
+ return this.bot.sendMarkdown(toSdkReplyTarget(target), text, { keyboard });
90
+ }
91
+ acknowledgeInteraction(id, code, data) {
92
+ return this.bot.acknowledgeInteraction(id, code, data);
93
+ }
94
+ async sendMedia(target, message) {
95
+ return this.bot.sendMedia({ target: toSdkReplyTarget(target), ...mediaOpts(message) });
96
+ }
97
+ openStream(target, options) {
98
+ return this.bot.openStream({
99
+ target: toSdkReplyTarget(target),
100
+ throttleMs: options.throttleMs,
101
+ });
102
+ }
103
+ }
104
+ /** Port `QQReplyTarget`/`QQStreamTarget` is structurally identical to the SDK
105
+ * `ReplyTarget`; narrow explicitly so the SDK methods accept it. */
106
+ function toSdkReplyTarget(target) {
107
+ return { scope: target.scope, targetId: target.targetId, msgId: target.msgId };
108
+ }
109
+ export function mediaOpts(message) {
110
+ for (const part of message.parts ?? []) {
111
+ switch (part.type) {
112
+ case 'image':
113
+ if (part.url || part.dataUri || part.localData !== undefined) {
114
+ return {
115
+ fileType: MediaFileType.IMAGE,
116
+ ...mediaSource(part),
117
+ content: message.text,
118
+ };
119
+ }
120
+ break;
121
+ case 'audio':
122
+ if (part.url || part.dataUri || part.localData !== undefined) {
123
+ return { fileType: MediaFileType.VOICE, ...mediaSource(part) };
124
+ }
125
+ break;
126
+ case 'video':
127
+ if (part.url || part.dataUri || part.localData !== undefined) {
128
+ return { fileType: MediaFileType.VIDEO, ...mediaSource(part) };
129
+ }
130
+ break;
131
+ case 'file':
132
+ if (part.url || part.dataUri || part.localData !== undefined) {
133
+ return {
134
+ fileType: MediaFileType.FILE,
135
+ ...mediaSource(part),
136
+ fileName: part.name,
137
+ content: message.text,
138
+ };
139
+ }
140
+ break;
141
+ default:
142
+ break;
143
+ }
144
+ }
145
+ // No resolvable media part — fall back to a FILE with no source (caller
146
+ // guarantees a media part; this branch is defensive only).
147
+ return { fileType: MediaFileType.FILE, content: message.text };
148
+ }
149
+ /**
150
+ * Resolve a media part to the SDK's single-source shape (`url` or `fileData`).
151
+ *
152
+ * Carrier precedence: trusted bytes in hand win, then an
153
+ * inline data URI, then a genuine `http(s)` `url`. `localData` /
154
+ * `dataUri` are both base64-encoded for the QQ `uploadMedia` `fileData`
155
+ * field — never sent as `url` (the QQ upload API fetches `url` over HTTP and
156
+ * cannot resolve inline/raw bytes). Only a real `http(s)` `url` is passed
157
+ * through as `url`.
158
+ */
159
+ function mediaSource(part) {
160
+ if (part.localData !== undefined) {
161
+ return { fileData: Buffer.from(part.localData).toString('base64') };
162
+ }
163
+ if (part.dataUri) {
164
+ return { fileData: decodeDataUri(part.dataUri) };
165
+ }
166
+ return { url: part.url };
167
+ }
168
+ /**
169
+ * Decode a `data:<mime>;base64,<payload>` URI into the raw base64 payload the
170
+ * Tencent `uploadMedia` `fileData` field expects. Rejects non-base64 data URIs
171
+ * (the QQ upload API ingests raw base64, not arbitrary URL-encoded payloads).
172
+ */
173
+ export function decodeDataUri(dataUri) {
174
+ const match = /^data:[^;,]*;base64,([\s\S]+)$/.exec(dataUri);
175
+ const payload = match?.[1];
176
+ if (!payload) {
177
+ throw new ChannelSendError('unsupported media data URI');
178
+ }
179
+ return payload;
180
+ }
181
+ /** Offline fake SDK client: records calls and exposes controllable emits. */
182
+ export class FakeQQSdkClient {
183
+ readyHandlers = [];
184
+ resumedHandlers = [];
185
+ errorHandlers = [];
186
+ messageHandlers = [];
187
+ interactionHandlers = [];
188
+ started = false;
189
+ stopped = false;
190
+ /** If set, `start()` throws this error (never resolves). */
191
+ startError;
192
+ /** If true, `start()` resolves only when the signal aborts. */
193
+ hangStart = false;
194
+ /** If true, `start()` emits `ready` (then still resolves immediately). */
195
+ autoReady = false;
196
+ /** Last signal passed to `start()`. */
197
+ lastSignal;
198
+ textCalls = [];
199
+ keyboardCalls = [];
200
+ mediaCalls = [];
201
+ acknowledgeCalls = [];
202
+ streamCalls = [];
203
+ /** The live stream sessions opened so far (same order as `streamCalls`). */
204
+ streams = [];
205
+ /** If set, `sendText`/`sendMedia` reject with this error. */
206
+ sendError;
207
+ onReady(handler) {
208
+ this.readyHandlers.push(handler);
209
+ }
210
+ onResumed(handler) {
211
+ this.resumedHandlers.push(handler);
212
+ }
213
+ onError(handler) {
214
+ this.errorHandlers.push(handler);
215
+ }
216
+ onMessage(handler) {
217
+ this.messageHandlers.push(handler);
218
+ }
219
+ onInteraction(handler) {
220
+ this.interactionHandlers.push(handler);
221
+ }
222
+ async start(signal) {
223
+ this.started = true;
224
+ this.lastSignal = signal;
225
+ if (this.startError)
226
+ throw this.startError;
227
+ if (this.autoReady)
228
+ this.emitReady();
229
+ if (this.hangStart) {
230
+ await new Promise((resolve) => {
231
+ signal.addEventListener('abort', () => resolve(), { once: true });
232
+ });
233
+ return;
234
+ }
235
+ }
236
+ stop() {
237
+ this.stopped = true;
238
+ // Mirror the real SDK teardown: once stopped, no more events are
239
+ // dispatched to registered handlers.
240
+ this.readyHandlers = [];
241
+ this.resumedHandlers = [];
242
+ this.errorHandlers = [];
243
+ this.messageHandlers = [];
244
+ this.interactionHandlers = [];
245
+ }
246
+ async sendText(target, text) {
247
+ if (this.sendError)
248
+ throw this.sendError;
249
+ this.textCalls.push({ target, text });
250
+ return { id: `out-${this.textCalls.length}` };
251
+ }
252
+ async sendMarkdownWithKeyboard(target, text, keyboard) {
253
+ if (this.sendError)
254
+ throw this.sendError;
255
+ this.keyboardCalls.push({ target, text, keyboard, format: 'markdown' });
256
+ return { id: `out-kbd-${this.keyboardCalls.length}` };
257
+ }
258
+ async sendMedia(target, message) {
259
+ if (this.sendError)
260
+ throw this.sendError;
261
+ this.mediaCalls.push({ target, message });
262
+ return { upload: {}, message: { id: `out-media-${this.mediaCalls.length}` } };
263
+ }
264
+ async acknowledgeInteraction(id, code, data) {
265
+ this.acknowledgeCalls.push({ id, code, data });
266
+ return undefined;
267
+ }
268
+ openStream(target, options) {
269
+ this.streamCalls.push({ target, options });
270
+ const session = new FakeStreamSession();
271
+ this.streams.push(session);
272
+ return session;
273
+ }
274
+ // —— controllable emit helpers (tests) ——
275
+ emitReady() {
276
+ for (const h of this.readyHandlers)
277
+ h();
278
+ }
279
+ emitResumed() {
280
+ for (const h of this.resumedHandlers)
281
+ h();
282
+ }
283
+ emitError(error) {
284
+ for (const h of this.errorHandlers)
285
+ h(error);
286
+ }
287
+ emitMessage(message) {
288
+ for (const h of this.messageHandlers)
289
+ h(message);
290
+ }
291
+ emitInteraction(event) {
292
+ for (const h of this.interactionHandlers)
293
+ h(event);
294
+ }
295
+ }
296
+ /** Minimal offline `QQStreamSession` double for fake-client tests. */
297
+ export class FakeStreamSession {
298
+ updates = [];
299
+ completed = false;
300
+ cancelled = false;
301
+ async update(fullText) {
302
+ this.updates.push(fullText);
303
+ }
304
+ async complete() {
305
+ this.completed = true;
306
+ return { id: 'stream-final', timestamp: Date.now() };
307
+ }
308
+ cancel() {
309
+ this.cancelled = true;
310
+ }
311
+ }
312
+ //# sourceMappingURL=sdk-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-client.js","sourceRoot":"","sources":["../src/sdk-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `QQStreamingReply` — DSH `ReplyHandle` over a Tencent `StreamSession`.
3
+ *
4
+ * The Tencent stream API is **replace semantics**: `update(fullText)` always
5
+ * receives the full accumulated text (never a delta). This class accumulates
6
+ * deltas locally and forwards the monotonic full text on every change.
7
+ */
8
+ import type { ReplyHandle, OutboundMessage } from '@krischoichoi/channel-core';
9
+ import type { QQStreamSession } from './sdk-client.js';
10
+ export declare class QQStreamingReply implements ReplyHandle {
11
+ private readonly stream;
12
+ private text;
13
+ constructor(stream: QQStreamSession);
14
+ append(delta: string): Promise<void>;
15
+ replace(message: OutboundMessage): Promise<void>;
16
+ finish(message?: OutboundMessage): Promise<void>;
17
+ fail(): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=streaming-reply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-reply.d.ts","sourceRoot":"","sources":["../src/streaming-reply.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,qBAAa,gBAAiB,YAAW,WAAW;IAGtC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,IAAI,CAAM;gBAEW,MAAM,EAAE,eAAe;IAE9C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpC,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhD,MAAM,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
@@ -0,0 +1,26 @@
1
+ export class QQStreamingReply {
2
+ stream;
3
+ text = '';
4
+ constructor(stream) {
5
+ this.stream = stream;
6
+ }
7
+ async append(delta) {
8
+ this.text += delta;
9
+ await this.stream.update(this.text);
10
+ }
11
+ async replace(message) {
12
+ this.text = message.text ?? '';
13
+ await this.stream.update(this.text);
14
+ }
15
+ async finish(message) {
16
+ if (message?.text !== undefined && message.text !== this.text) {
17
+ this.text = message.text;
18
+ await this.stream.update(this.text);
19
+ }
20
+ await this.stream.complete();
21
+ }
22
+ async fail() {
23
+ this.stream.cancel();
24
+ }
25
+ }
26
+ //# sourceMappingURL=streaming-reply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-reply.js","sourceRoot":"","sources":["../src/streaming-reply.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,gBAAgB;IAGE;IAFrB,IAAI,GAAG,EAAE,CAAC;IAElB,YAA6B,MAAuB;QAAvB,WAAM,GAAN,MAAM,CAAiB;IAAG,CAAC;IAExD,KAAK,CAAC,MAAM,CAAC,KAAa;QACxB,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC;QACnB,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAwB;QACpC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAyB;QACpC,IAAI,OAAO,EAAE,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;YAC9D,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YACzB,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@krischoichoi/channel-qq",
3
+ "version": "0.6.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/krischoichoi/dsh-channels.git"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "description": "QQ channel adapter for DeepSeek Harness (Tencent official SDK, native C2C streaming + buffered group)",
12
+ "type": "module",
13
+ "main": "./lib/index.js",
14
+ "types": "./lib/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "lib"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "typecheck": "tsc -p tsconfig.json --noEmit",
28
+ "test": "vitest run --passWithNoTests",
29
+ "clean": "rimraf lib"
30
+ },
31
+ "dependencies": {
32
+ "@deepseek-ai/cordis": "^4.0.4",
33
+ "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
34
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
35
+ "@deepseek-ai/schemastery": "~3.18.4",
36
+ "@tencent-connect/qqbot-nodejs": "1.0.4",
37
+ "@krischoichoi/channel-control": "workspace:*",
38
+ "@krischoichoi/channel-core": "workspace:*",
39
+ "zod": "^4.4.3"
40
+ },
41
+ "devDependencies": {
42
+ "@krischoichoi/channel-testkit": "workspace:*",
43
+ "rimraf": "^6.0.0"
44
+ },
45
+ "license": "MIT"
46
+ }