dsh-qqbot 1.0.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.
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-qqbot`.
4
+ * @module @deepseek-ai/dsh-qqbot/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-qqbot";
7
+ /** Cordis companion plugin name. */
8
+ const name = "qqbot-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: this transport bridge owns no durable package-local
13
+ * event stream; protocol and bridge lifecycle tests cover its mapping.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,12 @@
1
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { QqbotSettingsInjected } from './index.ts';
3
+ /** Full settings.section component props: runtime share + injected share + the locale seat. */
4
+ export type QqbotSettingsSectionProps = PropsRuntime<'settings.section'> & InjectFace<QqbotSettingsInjected> & PropsLocale<'qqbot'>;
5
+ /**
6
+ * The QQ Bot settings page: AppID, AppSecret, and sandbox fields over the
7
+ * Host's redacted namespace view. The AppSecret is write-only on the wire, so
8
+ * the field renders blank and "keep unchanged" is the empty-value meaning;
9
+ * the clear button removes a stored secret. Saving applies every changed
10
+ * field in one ordered write, and the Host restarts a live gateway in place.
11
+ */
12
+ export declare function QqbotSettingsSection({ useQqbotSettings, useQqbotBound, save, t }: QqbotSettingsSectionProps): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { QqbotToggleInjected } from './index.ts';
3
+ /** Full input-left seat component props: runtime share & injected share & the locale seat. */
4
+ export type QqbotToggleProps = PropsRuntime<'conversation.input.left'> & InjectFace<QqbotToggleInjected> & PropsLocale<'qqbot'>;
5
+ /**
6
+ * Per-session QQ connection toggle over the shared bound-session observable.
7
+ * It reports the host's last admitted bind: `active` is the single bound
8
+ * session, not optimistic click state, so switching sessions shows the truth.
9
+ */
10
+ export declare function QqbotToggle({ sessionId, useQqbotBound, toggle, t }: QqbotToggleProps): import("react").JSX.Element;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * QQ bot plugin, browser half: occupies the composer tool row's always-visible
3
+ * `conversation.input.left` seat with a per-session connection toggle, and
4
+ * registers the QQ Bot page into the settings panel's `settings.section`
5
+ * navigation. The bound-session id and the settings namespace view are shared
6
+ * bare observables held in the inject faces' `hooks` compartments (the
7
+ * renderer binds them to `useQqbotBound`/`useQqbotSettings`); toggling
8
+ * executes /qqbot on | off against the addressed session through
9
+ * `remote.commands`, and settings edits write through the Host's settings
10
+ * wire face.
11
+ */
12
+ import { type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
13
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
14
+ import { type QqbotSettingsPatch, type QqbotSettingsState } from './settings-controller.ts';
15
+ import { type QqbotKey } from './locales.ts';
16
+ export type { QqbotSettingsPatch, QqbotSettingsState } from './settings-controller.ts';
17
+ export type { QqbotKey } from './locales.ts';
18
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
19
+ interface LocaleNamespaceMap {
20
+ /** The connection toggle's and settings page's copy. */
21
+ qqbot: QqbotKey;
22
+ }
23
+ }
24
+ /** Shared bound-session state: the id of the session currently bridged to QQ. */
25
+ export interface QqbotBoundState {
26
+ readonly sessionId: string | null;
27
+ }
28
+ /** Injected business face of the input-left seat. */
29
+ export interface QqbotToggleInjected {
30
+ /** Reserved observable compartment; binds to `useQqbotBound` on the component. */
31
+ hooks: {
32
+ qqbotBound: SnapshotStore<QqbotBoundState>;
33
+ };
34
+ /**
35
+ * Connect (if idle) or disconnect (if this session is bound) via /qqbot.
36
+ * @returns null on admitted execution; a user-visible failure line otherwise.
37
+ */
38
+ toggle: () => Promise<string | null>;
39
+ }
40
+ /** Injected business face of the settings section seat. */
41
+ export interface QqbotSettingsInjected {
42
+ /** Reserved observable compartments; bind to `useQqbotSettings`/`useQqbotBound`. */
43
+ hooks: {
44
+ qqbotSettings: SnapshotStore<QqbotSettingsState>;
45
+ qqbotBound: SnapshotStore<QqbotBoundState>;
46
+ };
47
+ /**
48
+ * Apply one user edit to the qqbot settings namespace.
49
+ * @returns null on success; the Host's failure text otherwise.
50
+ */
51
+ save: (patch: QqbotSettingsPatch) => Promise<string | null>;
52
+ }
53
+ /** Required services: the seats' slot registry, connection and commands remotes, and locale. */
54
+ export declare const inject: string[];
55
+ /**
56
+ * Client plugin body: register the connection toggle over the command channel
57
+ * and the QQ Bot settings page over the settings wire face.
58
+ * @param ctx - client root context.
59
+ */
60
+ export declare function apply(ctx: ClientContext): void;
@@ -0,0 +1,63 @@
1
+ /** `qqbot` namespace dictionaries (the composer toggle's and settings page's copy). */
2
+ /** Simplified Chinese dictionary (the key-set source of truth). */
3
+ export declare const zh: {
4
+ 'toggle.on.aria': string;
5
+ 'toggle.on.title': string;
6
+ 'toggle.on.label': string;
7
+ 'toggle.off.aria': string;
8
+ 'toggle.off.title': string;
9
+ 'toggle.off.label': string;
10
+ 'settings.nav': string;
11
+ 'settings.title': string;
12
+ 'settings.desc': string;
13
+ 'appId.label': string;
14
+ 'appId.placeholder': string;
15
+ 'appId.hint': string;
16
+ 'appSecret.label': string;
17
+ 'appSecret.placeholder': string;
18
+ 'appSecret.placeholder.set': string;
19
+ 'appSecret.hint': string;
20
+ 'appSecret.hint.set': string;
21
+ 'clear.label': string;
22
+ 'sandbox.label': string;
23
+ 'sandbox.hint': string;
24
+ 'save.label': string;
25
+ 'save.busy': string;
26
+ 'save.ok': string;
27
+ 'bound.prefix': string;
28
+ 'status.loading': string;
29
+ 'status.error': string;
30
+ 'status.readonly': string;
31
+ };
32
+ /** The qqbot namespace key union. */
33
+ export type QqbotKey = keyof typeof zh;
34
+ /** English dictionary, checked complete against the zh key set. */
35
+ export declare const en: {
36
+ 'toggle.on.aria': string;
37
+ 'toggle.on.title': string;
38
+ 'toggle.on.label': string;
39
+ 'toggle.off.aria': string;
40
+ 'toggle.off.title': string;
41
+ 'toggle.off.label': string;
42
+ 'settings.nav': string;
43
+ 'settings.title': string;
44
+ 'settings.desc': string;
45
+ 'appId.label': string;
46
+ 'appId.placeholder': string;
47
+ 'appId.hint': string;
48
+ 'appSecret.label': string;
49
+ 'appSecret.placeholder': string;
50
+ 'appSecret.placeholder.set': string;
51
+ 'appSecret.hint': string;
52
+ 'appSecret.hint.set': string;
53
+ 'clear.label': string;
54
+ 'sandbox.label': string;
55
+ 'sandbox.hint': string;
56
+ 'save.label': string;
57
+ 'save.busy': string;
58
+ 'save.ok': string;
59
+ 'bound.prefix': string;
60
+ 'status.loading': string;
61
+ 'status.error': string;
62
+ 'status.readonly': string;
63
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Settings-page transport for the qqbot namespace: reads the Host's redacted
3
+ * settings view and writes user edits as path ops, mirroring the result in a
4
+ * snapshot store the section renders through. The Host is the single fact
5
+ * source — every mutation writes through the wire and the page re-renders
6
+ * from the response view (or a reload after a refusal).
7
+ */
8
+ import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client';
9
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
+ /** The settings namespace the Host plugin registers its identity under. */
11
+ export declare const QQBOT_SETTINGS_NAMESPACE = "qqbot";
12
+ /** One user edit the section sends: absent fields stay untouched. */
13
+ export interface QqbotSettingsPatch {
14
+ /** New AppID; an empty string clears the field (re-inherits the base layer). */
15
+ appId?: string;
16
+ /** New AppSecret; omitted or empty keeps the stored value. */
17
+ appSecret?: string;
18
+ /** New sandbox flag. */
19
+ sandbox?: boolean;
20
+ /** Remove the stored AppSecret. */
21
+ clearSecret?: boolean;
22
+ }
23
+ /** Snapshot state mirrored from the Host's redacted namespace view. */
24
+ export interface QqbotSettingsState {
25
+ /** `loading` until the first accepted view; `error` after a failed load. */
26
+ status: 'idle' | 'loading' | 'ready' | 'error';
27
+ /** Resolved AppID (base layer overridden by the user section); '' when unset. */
28
+ appId: string;
29
+ /** Resolved sandbox flag. */
30
+ sandbox: boolean;
31
+ /** Whether the user section stores an AppSecret (the value never rides the wire). */
32
+ secretSet: boolean;
33
+ /** Whether the Host settings document accepts writes. */
34
+ writable: boolean;
35
+ /** Namespace revision the next write fences against. */
36
+ revision: number | undefined;
37
+ /** Human failure text of the latest load or save; null while healthy. */
38
+ error: string | null;
39
+ }
40
+ /**
41
+ * Serializes the qqbot namespace's Host reads and writes behind one snapshot
42
+ * store. Latest load wins; a refused save reloads the namespace so the form
43
+ * re-renders from the fresh view and the user can re-apply the edit.
44
+ */
45
+ export declare class QqbotSettingsController {
46
+ private readonly api;
47
+ /** uSES-safe state source the section renders through its hooks compartment. */
48
+ readonly store: SnapshotStore<QqbotSettingsState>;
49
+ private generation;
50
+ /**
51
+ * @param api - the settings wire face.
52
+ */
53
+ constructor(api: Pick<IApiClient, 'settings'>);
54
+ /**
55
+ * Refresh the snapshot from `settings.describe`; a failure keeps the last
56
+ * good state and surfaces the error.
57
+ * @returns settlement after the read.
58
+ */
59
+ load(): Promise<void>;
60
+ /**
61
+ * Apply one user edit as ordered path ops; a refusal reloads the namespace
62
+ * and returns its message.
63
+ * @param patch - the fields the user changed.
64
+ * @returns null on success; the Host's failure text otherwise.
65
+ */
66
+ save(patch: QqbotSettingsPatch): Promise<string | null>;
67
+ /** Fold one redacted wire view into the snapshot. */
68
+ private accept;
69
+ }
70
+ /**
71
+ * Refresh only after the section left idle, so an unopened page never fetches
72
+ * on background invalidations.
73
+ * @param controller - the page controller whose current status gates the reload.
74
+ */
75
+ export declare function refreshIfLoaded(controller: QqbotSettingsController): void;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * QQ official bot gateway client: token refresh, WebSocket connection,
3
+ * heartbeat, reconnect, inbound dispatch, and outbound message sending.
4
+ * The socket and fetch implementations are injectable so tests can drive the
5
+ * state machine without a live platform.
6
+ * @module @deepseek-ai/dsh-qqbot/gateway
7
+ */
8
+ import type { FetchLike, QqCredentials, QqMessage } from './types.ts';
9
+ /** Minimal logger surface the gateway uses; `ctx.logger` satisfies it. */
10
+ export interface QqGatewayLogger {
11
+ warn(message: string): void;
12
+ info?(message: string): void;
13
+ debug?(message: string): void;
14
+ }
15
+ /** The WebSocket handle surface the gateway drives. */
16
+ export interface QqSocketLike {
17
+ onopen: (() => void) | null;
18
+ onmessage: ((event: {
19
+ data: unknown;
20
+ }) => void) | null;
21
+ onclose: ((event: {
22
+ code?: number;
23
+ }) => void) | null;
24
+ onerror: ((error: unknown) => void) | null;
25
+ send(data: string): void;
26
+ close(code?: number): void;
27
+ }
28
+ /** Construction options; every field beyond creds has a production default. */
29
+ export interface QqGatewayOptions {
30
+ readonly creds: QqCredentials;
31
+ readonly sandbox: boolean;
32
+ readonly onMessage: (message: QqMessage) => void;
33
+ readonly logger?: QqGatewayLogger;
34
+ readonly fetchImpl?: FetchLike;
35
+ readonly socketFactory?: (url: string) => QqSocketLike;
36
+ /** Minimum refresh margin before a token is considered expired (ms). */
37
+ readonly tokenMarginMs?: number;
38
+ /** Maximum reconnect backoff (ms). */
39
+ readonly maxReconnectDelayMs?: number;
40
+ /** Epoch-ms clock, for token-expiry and backoff tests. */
41
+ readonly now?: () => number;
42
+ }
43
+ /**
44
+ * Long-lived connection to the QQ gateway. `start()` connects once; a drop
45
+ * after READY schedules an exponential-backoff reconnect. The client owns the
46
+ * access token and exposes {@link sendText} for reply delivery.
47
+ */
48
+ export declare class QqGatewayClient {
49
+ private readonly creds;
50
+ private readonly sandbox;
51
+ private readonly onMessage;
52
+ private readonly logger;
53
+ private readonly fetchImpl;
54
+ private readonly socketFactory;
55
+ private readonly tokenMarginMs;
56
+ private readonly maxReconnectDelayMs;
57
+ private readonly now;
58
+ private accessToken;
59
+ private tokenExpiresAt;
60
+ private socket;
61
+ private heartbeatTimer;
62
+ private reconnectTimer;
63
+ private seq;
64
+ private reconnectAttempts;
65
+ private closed;
66
+ constructor(options: QqGatewayOptions);
67
+ /**
68
+ * Return a usable access token, refreshing it when absent or near expiry.
69
+ * @returns the current access token.
70
+ */
71
+ ensureToken(): Promise<string>;
72
+ /** Connect (and, on a later drop, reconnect) to the gateway. */
73
+ start(): Promise<void>;
74
+ /**
75
+ * Permanently close the client: stop timers, cancel reconnects, and close
76
+ * the live socket. Idempotent.
77
+ */
78
+ close(): void;
79
+ /**
80
+ * Deliver one reply to a chat. Chunks the text to the platform limit and
81
+ * posts each chunk with the triggering message id threaded as a reply.
82
+ * @param path - the REST path from {@link groupMessagesPath} or {@link c2cMessagesPath}.
83
+ * @param text - the reply text.
84
+ * @param msgId - the inbound message id being answered.
85
+ */
86
+ sendText(path: string, text: string, msgId: string): Promise<void>;
87
+ /** Mint a token, resolve the gateway URL, and complete one socket session. */
88
+ private connectOnce;
89
+ /** Open the socket and resolve once READY (or RESUME) arrives. */
90
+ private openSocket;
91
+ /** Start the heartbeat interval, replacing any previous one. */
92
+ private startHeartbeat;
93
+ private stopHeartbeat;
94
+ /** Schedule one reconnect attempt with exponential backoff. */
95
+ private scheduleReconnect;
96
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * QQ official bot bridge for DeepSeek Harness.
3
+ *
4
+ * Answers @-mentions in group chat and private C2C messages by routing them
5
+ * into one harness session the user selects from the conversation window. The
6
+ * `/qqbot on` command binds the dispatching agent (its context and cwd) to the
7
+ * QQ gateway; `/qqbot off` disconnects. Nothing connects until a session opts
8
+ * in, and QQ messages then arrive as ordinary user messages on that agent.
9
+ *
10
+ * The robot identity (AppID/AppSecret) lives in the `qqbot` settings section —
11
+ * the Settings → QQ Bot page of the web GUI — with the composition entry as
12
+ * the base layer and `QQBOT_APP_ID`/`QQBOT_APP_SECRET` as an environment
13
+ * fallback. Committed settings changes restart a live gateway in place.
14
+ *
15
+ * The bridge is also an approval answerer for its bound agent: a tool call that
16
+ * asks for confirmation is forwarded to the originating QQ chat as a prompt,
17
+ * and the user's "允许" / "拒绝" reply settles the request.
18
+ *
19
+ * @module dsh-qqbot
20
+ */
21
+ import type { Context } from '@deepseek-ai/cordis';
22
+ import Schema from '@deepseek-ai/schemastery';
23
+ import type { ApprovalOutcome } from '@deepseek-ai/dsh-user-approval';
24
+ import type { QqbotConfig } from './types.ts';
25
+ export type { QqbotConfig } from './types.ts';
26
+ export declare const name = "qqbot";
27
+ /** The bridge binds the session's existing agent; commands and session events drive it. */
28
+ export declare const inject: string[];
29
+ /** Plugin config: robot identity plus the API environment selection. */
30
+ export declare const Config: Schema<QqbotConfig>;
31
+ /** Environment variable fallback for the robot application id. */
32
+ export declare const QQBOT_APP_ID_ENV = "QQBOT_APP_ID";
33
+ /** Environment variable fallback for the robot application secret. */
34
+ export declare const QQBOT_APP_SECRET_ENV = "QQBOT_APP_SECRET";
35
+ /** Settings namespace carrying the robot identity (Settings → QQ Bot). */
36
+ export declare const QQBOT_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
37
+ /** Prompt section name registered on the bound agent's scope. */
38
+ export declare const QQBOT_CHANNEL_SECTION = "qqbot:channel";
39
+ /** Prompt section order: after the persona, before tool guidance. */
40
+ export declare const QQBOT_CHANNEL_ORDER = 10;
41
+ /**
42
+ * The QQ-channel instructions, registered verbatim on the bound agent while a
43
+ * session is connected. Kept as a literal export so the README and tests quote
44
+ * the exact text.
45
+ */
46
+ export declare const QQBOT_CHANNEL_PROMPT: string;
47
+ /**
48
+ * Parse an inbound QQ reply into an approval decision, or leave it undefined
49
+ * when it is not an answer. Matches the whole normalized text, then its first
50
+ * whitespace/punctuation-delimited token ("允许。" → allow, "同意 执行" → allow).
51
+ * @param text - the raw inbound message content.
52
+ * @returns the decision, or undefined when the text is not an answer.
53
+ */
54
+ export declare function parseAnswer(text: string): ApprovalOutcome | undefined;
55
+ /**
56
+ * Mount the QQ bot bridge. It registers the `/qqbot` command and connects the
57
+ * gateway only when a session runs `/qqbot on`; the bound agent's own scope,
58
+ * history, and cwd are reused unchanged. While bound, it also answers that
59
+ * agent's approval requests through the originating QQ chat.
60
+ * @param ctx - Cordis context carrying the agent registry, commands, and session events.
61
+ * @param config - composition entry; the user layer of the `qqbot` settings
62
+ * namespace resolves over it once a settings provider is mounted.
63
+ */
64
+ export declare function apply(ctx: Context, config: QqbotConfig): void;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-qqbot`.
3
+ * @module @deepseek-ai/dsh-qqbot/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "qqbot-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Pure QQ official bot protocol helpers: gateway opcodes, event intents,
3
+ * REST endpoints, access-token and gateway URL fetches, inbound dispatch
4
+ * parsing, and outbound text chunking. Everything here is I/O- and timer-free
5
+ * except the two `fetch` wrappers, so the rest is unit-testable directly.
6
+ * @module @deepseek-ai/dsh-qqbot/protocol
7
+ */
8
+ import type { FetchLike, QqAccessToken, QqCredentials, QqMessage } from './types.ts';
9
+ /** Gateway opcodes from the QQ official bot WebSocket protocol. */
10
+ export declare const QQ_OP: {
11
+ readonly DISPATCH: 0;
12
+ readonly HEARTBEAT: 1;
13
+ readonly IDENTIFY: 2;
14
+ readonly RESUME: 6;
15
+ readonly RECONNECT: 7;
16
+ readonly INVALID_SESSION: 9;
17
+ readonly HELLO: 10;
18
+ readonly HEARTBEAT_ACK: 11;
19
+ };
20
+ /**
21
+ * Intent bit for group @-mention and C2C private-message events. The single
22
+ * `1 << 25` (`GROUP_AND_C2C_EVENT`) intent delivers both, so the bridge
23
+ * subscribes to it and nothing else.
24
+ */
25
+ export declare const QQ_INTENT_GROUP_AND_C2C: number;
26
+ /** Access-token mint endpoint; independent of the sandbox selection. */
27
+ export declare const QQ_TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
28
+ /** Default outbound chunk length in UTF-16 code units. */
29
+ export declare const QQ_MAX_MESSAGE_LENGTH = 2000;
30
+ /**
31
+ * Resolve the REST API base for the selected environment.
32
+ * @param sandbox - whether the sandbox host is in use.
33
+ * @returns the `https://api.sgroup.qq.com` or sandbox equivalent origin.
34
+ */
35
+ export declare function apiBase(sandbox: boolean): string;
36
+ /**
37
+ * REST path for sending a message to a group.
38
+ * @param groupOpenid - the target group's openid.
39
+ * @returns the `/v2/groups/{groupOpenid}/messages` path.
40
+ */
41
+ export declare function groupMessagesPath(groupOpenid: string): string;
42
+ /**
43
+ * REST path for sending a private message to a user.
44
+ * @param userOpenid - the target user's openid.
45
+ * @returns the `/v2/users/{userOpenid}/messages` path.
46
+ */
47
+ export declare function c2cMessagesPath(userOpenid: string): string;
48
+ /**
49
+ * Mint an access token from the robot app id and secret.
50
+ * @param creds - the robot credentials.
51
+ * @param fetchImpl - injectable fetch (defaults to the global).
52
+ * @param now - injectable epoch-ms clock (defaults to `Date.now`).
53
+ * @returns the token with its absolute expiry.
54
+ */
55
+ export declare function fetchAccessToken(creds: QqCredentials, fetchImpl?: FetchLike, now?: () => number): Promise<QqAccessToken>;
56
+ /**
57
+ * Resolve the WebSocket gateway URL for the selected environment.
58
+ * @param accessToken - the current access token.
59
+ * @param sandbox - whether the sandbox host is in use.
60
+ * @param fetchImpl - injectable fetch (defaults to the global).
61
+ * @returns the gateway WebSocket URL.
62
+ */
63
+ export declare function fetchGatewayUrl(accessToken: string, sandbox: boolean, fetchImpl?: FetchLike): Promise<string>;
64
+ /** Raw shape extracted from one WebSocket text frame. */
65
+ export interface RawGatewayMessage {
66
+ readonly op: number;
67
+ readonly type?: string;
68
+ readonly seq?: number;
69
+ readonly data?: unknown;
70
+ readonly heartbeatInterval?: number;
71
+ }
72
+ /**
73
+ * Parse one gateway text frame into its opcode and the fields that op carries.
74
+ * @param raw - the frame payload (expected to be a JSON string).
75
+ * @returns the parsed message, or `undefined` for non-text or malformed frames.
76
+ */
77
+ export declare function parseRawGatewayMessage(raw: unknown): RawGatewayMessage | undefined;
78
+ /**
79
+ * Narrow a dispatch event to an answerable {@link QqMessage}. Group events
80
+ * require a `group_openid`; C2C events require an `author.user_openid`. Other
81
+ * event types (READY, RESUMED, and every non-message event) return `undefined`.
82
+ * @param type - the dispatch event type (`t`).
83
+ * @param data - the dispatch payload (`d`).
84
+ * @returns the normalized message, or `undefined` when the event is not answerable.
85
+ */
86
+ export declare function parseMessageEvent(type: string, data: unknown): QqMessage | undefined;
87
+ /**
88
+ * Strip QQ mention tags from group message text. The platform may deliver
89
+ * `<@!12345>` or `<@OPENID>`; both are removed so only the user's words remain.
90
+ * @param text - the raw group message content.
91
+ * @returns the text with mentions removed and surrounding whitespace trimmed.
92
+ */
93
+ export declare function stripMentions(text: string): string;
94
+ /**
95
+ * Split outbound text into chunks no longer than `max` UTF-16 code units.
96
+ * An empty input yields no chunks (nothing to send).
97
+ * @param text - the reply text.
98
+ * @param max - the per-chunk ceiling.
99
+ * @returns zero or more bounded chunks.
100
+ */
101
+ export declare function splitMessage(text: string, max?: number): string[];
102
+ /**
103
+ * Build the ordered request bodies for one reply. The first chunk carries the
104
+ * triggering `msg_id` so the platform renders it as a reply; subsequent chunks
105
+ * share it with an increasing `msg_seq`, streaming the multi-chunk reply.
106
+ * @param text - the reply text.
107
+ * @param msgId - the inbound message id being answered, when known.
108
+ * @returns the ordered request bodies, or an empty array for empty text.
109
+ */
110
+ export declare function buildSendPayloads(text: string, msgId: string | undefined): Array<Record<string, unknown>>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Vocabulary for the QQ official bot bridge: gateway credentials, token state,
3
+ * inbound message events, and plugin config. Pure types only — no runtime code.
4
+ * @module @deepseek-ai/dsh-qqbot/types
5
+ */
6
+ /** QQ official bot credentials used to mint the gateway access token. */
7
+ export interface QqCredentials {
8
+ /** The robot application id from the QQ open platform. */
9
+ readonly appId: string;
10
+ /** The robot application secret; kept out of logs and prompts. */
11
+ readonly appSecret: string;
12
+ }
13
+ /** A minted access token plus its absolute expiry timestamp (epoch ms). */
14
+ export interface QqAccessToken {
15
+ readonly accessToken: string;
16
+ /** Epoch milliseconds at which the token must be refreshed. */
17
+ readonly expiresAt: number;
18
+ }
19
+ /**
20
+ * Narrow fetch surface the bridge needs: a URL string plus optional init.
21
+ * The global `fetch` satisfies it, and tests can supply a plain function
22
+ * without a lossy `typeof fetch` cast.
23
+ */
24
+ export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
25
+ /** The `author` block shared by group and C2C message events. */
26
+ export interface QqAuthor {
27
+ /** Sender's platform-scoped numeric id; absent on C2C events. */
28
+ readonly id?: string;
29
+ /** Sender openid for C2C events. */
30
+ readonly userOpenid?: string;
31
+ /** Member openid within a group; present only on group events. */
32
+ readonly memberOpenid?: string;
33
+ /** Display name, when the platform supplies one. */
34
+ readonly username?: string;
35
+ }
36
+ /**
37
+ * One inbound message the bridge can answer. The two kinds carry the same
38
+ * content fields and differ only in who the reply is addressed to: the
39
+ * sender's `userOpenid` for C2C, or the group `groupOpenid` for group chat.
40
+ */
41
+ export type QqMessage = {
42
+ readonly kind: 'c2c';
43
+ readonly id: string;
44
+ readonly content: string;
45
+ readonly author: QqAuthor;
46
+ readonly userOpenid: string;
47
+ } | {
48
+ readonly kind: 'group';
49
+ readonly id: string;
50
+ readonly content: string;
51
+ readonly author: QqAuthor;
52
+ readonly groupOpenid: string;
53
+ };
54
+ /** Plugin config: QQ bot identity plus the API environment selection. */
55
+ export interface QqbotConfig {
56
+ /** Robot application id; falls back to `QQBOT_APP_ID`. */
57
+ readonly appId?: string;
58
+ /** Robot application secret; falls back to `QQBOT_APP_SECRET`. */
59
+ readonly appSecret?: string;
60
+ /** Route token, gateway, and sends through the sandbox host. Default false. */
61
+ readonly sandbox?: boolean;
62
+ }