//#region src/agp-types.d.ts /** * AGP (Agent Gateway Protocol) type definitions. * * Reverse-engineered from the wechat-access plugin at: * QClaw.app/Contents/Resources/openclaw/config/extensions/wechat-access/websocket/types.ts * * AGP is the WebSocket protocol between an OpenClaw agent client and the * WeChat gateway backend. All messages use a unified JSON "envelope" format. * * Message directions: * Downlink (server -> client): session.prompt, session.cancel * Uplink (client -> server): session.update, session.promptResponse */ /** Unified AGP message envelope. Every WS text frame is one of these. */ interface AGPEnvelope { /** Globally unique message ID (UUID v4), used for idempotent dedup */ msg_id: string; /** Device identifier (carried in downlink, echo back in uplink) */ guid?: string; /** User identifier (carried in downlink, echo back in uplink) */ user_id?: string; /** Message type / RPC method */ method: AGPMethod; /** Message payload (shape depends on method) */ payload: T; } /** * AGP method discriminator. * * - session.prompt: server sends user message to client * - session.cancel: server cancels an in-progress turn * - session.update: client streams intermediate chunks to server * - session.promptResponse: client sends final answer to server * - ping: application-level keepalive (rare, native ws ping preferred) */ type AGPMethod = "session.prompt" | "session.cancel" | "session.update" | "session.promptResponse" | "ping"; /** Content block. Currently only text is supported. */ interface ContentBlock { type: "text"; text: string; } /** Tool call lifecycle status. */ type ToolCallStatus = "pending" | "in_progress" | "completed" | "failed"; /** Semantic kind of tool operation, used for UI iconography. */ type ToolCallKind = "read" | "edit" | "delete" | "execute" | "search" | "fetch" | "think" | "other"; /** File/directory path associated with a tool call. */ interface ToolLocation { /** Absolute path to the file or directory */ path: string; } /** * Describes a single tool invocation. * * A tool call lifecycle produces multiple session.update messages: * 1. update_type=tool_call (status=in_progress -- tool starts) * 2. update_type=tool_call_update (status=in_progress -- optional intermediate state) * 3. update_type=tool_call_update (status=completed|failed -- final) */ interface ToolCall { /** Unique ID linking all updates for this tool invocation */ tool_call_id: string; /** Human-readable title (e.g. "read_file") */ title?: string; /** Semantic kind, for UI display */ kind?: ToolCallKind; /** Current lifecycle status */ status: ToolCallStatus; /** Tool output content (present when completed) */ content?: ContentBlock[]; /** File paths touched by this tool call */ locations?: ToolLocation[]; } /** * session.prompt payload -- server sends a user's message to the agent. * The client must eventually reply with session.promptResponse. */ interface PromptPayload { /** Session identifier (a conversation) */ session_id: string; /** Turn identifier (one user message + AI reply) */ prompt_id: string; /** Target agent application identifier */ agent_app: string; /** User's message content (array of ContentBlock, currently text only) */ content: ContentBlock[]; } /** * session.cancel payload -- server requests cancellation of an in-progress turn. * Client should abort processing and send promptResponse with stop_reason=cancelled. */ interface CancelPayload { session_id: string; prompt_id: string; agent_app: string; } /** * session.update sub-type discriminator. * * - message_chunk: incremental text fragment (streaming output) * - tool_call: tool invocation started * - tool_call_update: tool invocation status changed */ type UpdateType = "message_chunk" | "tool_call" | "tool_call_update"; /** * session.update payload -- streaming intermediate state. * * Depending on update_type: * - message_chunk: uses `content` (single ContentBlock, NOT an array) * - tool_call / tool_call_update: uses `tool_call` */ interface UpdatePayload { session_id: string; prompt_id: string; update_type: UpdateType; /** Text chunk (for update_type=message_chunk). Single block, not an array. */ content?: ContentBlock; /** Tool call info (for update_type=tool_call or tool_call_update) */ tool_call?: ToolCall; } /** * Reason the agent stopped generating. * * - end_turn: normal completion * - cancelled: user/server cancelled * - refusal: agent policy refusal * - error: technical error */ type StopReason = "end_turn" | "cancelled" | "refusal" | "error"; /** * session.promptResponse payload -- final answer for a turn. * Must be sent for every prompt, even on cancellation/error. * The server won't accept a new prompt until this is received. */ interface PromptResponsePayload { session_id: string; prompt_id: string; stop_reason: StopReason; /** Final response content (present when stop_reason=end_turn) */ content?: ContentBlock[]; /** Error description (present when stop_reason=error|refusal) */ error?: string; } /** Downlink: session.prompt */ type PromptMessage = AGPEnvelope; /** Downlink: session.cancel */ type CancelMessage = AGPEnvelope; /** Uplink: session.update */ type UpdateMessage = AGPEnvelope; /** Uplink: session.promptResponse */ type PromptResponseMessage = AGPEnvelope; /** Connection state machine states. */ type ConnectionState = "disconnected" | "connecting" | "connected" | "reconnecting"; /** Configuration for AGPClient. */ interface AGPClientConfig { /** * WebSocket endpoint URL. * Production: wss://mmgrcalltoken.3g.qq.com/agentwss * Test: wss://jprx.sparta.html5.qq.com/agentwss */ url: string; /** Authentication token (appended as ?token= query param) */ token: string; /** Device GUID -- echoed back in uplink messages */ guid?: string; /** User ID -- echoed back in uplink messages */ userId?: string; /** * Base reconnect interval in ms (default 3000). * Actual delay uses exponential backoff: base * 1.5^(attempt-1), capped at 25s. */ reconnectInterval?: number; /** * Maximum reconnect attempts (default 0 = infinite). */ maxReconnectAttempts?: number; /** * Heartbeat (ws ping) interval in ms (default 20000). * Should be less than the server's idle timeout (~60s). */ heartbeatInterval?: number; } /** Event callbacks for AGPClient. */ interface AGPClientCallbacks { /** WebSocket connection established */ onConnected?: () => void; /** WebSocket connection lost */ onDisconnected?: (reason?: string) => void; /** Received session.prompt (user sent a message) */ onPrompt?: (message: PromptMessage) => void; /** Received session.cancel (user cancelled) */ onCancel?: (message: CancelMessage) => void; /** An error occurred */ onError?: (error: Error) => void; } //#endregion //#region src/agp-client.d.ts declare class AGPClient { private readonly url; private token; private readonly guid; private readonly userId; private readonly reconnectInterval; private readonly maxReconnectAttempts; private readonly heartbeatInterval; private callbacks; private ws; private state; private reconnectTimer; private heartbeatTimer; private wakeupCheckTimer; private msgIdCleanupTimer; private reconnectAttempts; private lastPongTime; private lastTickTime; private processedMsgIds; constructor(config: AGPClientConfig, callbacks?: AGPClientCallbacks); /** Start the WebSocket connection. No-op if already connected/connecting. */ start(): void; /** * Gracefully stop. Closes the socket, cancels all timers, * and prevents automatic reconnection. */ stop(): void; /** Current connection state. */ getState(): ConnectionState; /** Replace the auth token (e.g. after a refresh). Takes effect on next connect. */ setToken(token: string): void; /** Merge in new callbacks (existing ones are preserved if not overridden). */ setCallbacks(callbacks: Partial): void; /** * Send a streaming text chunk (session.update, update_type=message_chunk). * * Call this repeatedly as your agent generates text. Each call sends * only the *new* incremental text, not the full accumulated response. */ sendMessageChunk(sessionId: string, promptId: string, text: string, guid?: string, userId?: string): void; /** * Notify that a tool call has started (session.update, update_type=tool_call). */ sendToolCall(sessionId: string, promptId: string, toolCall: ToolCall, guid?: string, userId?: string): void; /** * Update a tool call's status (session.update, update_type=tool_call_update). */ sendToolCallUpdate(sessionId: string, promptId: string, toolCall: ToolCall, guid?: string, userId?: string): void; /** * Send the final response for a turn (session.promptResponse). * * This MUST be sent for every prompt -- even on cancellation or error. * The server will not send a new prompt until it receives this. */ sendPromptResponse(payload: PromptResponsePayload, guid?: string, userId?: string): void; /** * Convenience: send a successful end-turn response with text content. */ sendTextResponse(sessionId: string, promptId: string, text: string, guid?: string, userId?: string): void; /** * Convenience: send an error response for a turn. */ sendErrorResponse(sessionId: string, promptId: string, errorMessage: string, guid?: string, userId?: string): void; /** * Convenience: send a cancellation acknowledgement for a turn. */ sendCancelledResponse(sessionId: string, promptId: string, guid?: string, userId?: string): void; private connect; private buildConnectionUrl; private setupEventHandlers; private handleOpen; private handleRawMessage; private handleClose; private handlePong; private handleError; private handleConnectionError; private scheduleReconnect; private clearReconnectTimer; private startHeartbeat; private clearHeartbeat; private startWakeupDetection; private clearWakeupDetection; private sendEnvelope; private startMsgIdCleanup; private clearMsgIdCleanup; } //#endregion //#region src/index.d.ts /** * QClaw WeChat Access API Client * * Reverse-engineered from QClaw.app (Electron, asar unencrypted). * Implements the full jprx gateway protocol used by the renderer's * `openclawApiService` class (tS) found in platform-QEsQ5tXh.js. * * Usage: * const client = new QClawClient({ env: "production" }); * const state = await client.getWxLoginState({ guid: "..." }); * const login = await client.wxLogin({ guid: "...", code: "...", state: "..." }); */ type Environment = "production" | "test"; interface EnvUrls { jprxGateway: string; wxLoginRedirectUri: string; beaconUrl: string; qclawBaseUrl: string; wechatWsUrl: string; } interface WxLoginConfig { appid: string; redirect_uri: string; wxLoginStyleBase64: string; } interface ClientOptions { /** "production" (default) or "test" */ env?: Environment; /** Persisted JWT from a previous session */ jwtToken?: string; /** User info restored from a previous session */ userInfo?: UserInfo | null; /** Override the web_version sent in every request body (default "1.4.0") */ webVersion?: string; } interface UserInfo { nickname: string; avatar: string; guid: string; userId: string; loginKey?: string; [key: string]: unknown; } interface ApiResponse { success: boolean; code?: number; message: string; data: T | null; } interface WxLoginStateData { state: string; [key: string]: unknown; } interface WxLoginUserInfo { nickname?: string; avatar?: string; avatar_url?: string; user_id?: string; [key: string]: unknown; } interface WxLoginData { token: string; openclaw_channel_token: string; user_info?: WxLoginUserInfo; /** @deprecated Flat fields may not exist; use user_info instead */ nickname?: string; avatar?: string; userId?: string; guid?: string; loginKey?: string; [key: string]: unknown; } interface UserInfoData { nickname: string; avatar?: string; head_img_url?: string; head_img?: string; nick_name?: string; guid: string; userId?: string; user_id?: string; [key: string]: unknown; } interface ApiKeyData { key: string; [key: string]: unknown; } interface InviteCodeStatus { verified: boolean; [key: string]: unknown; } interface ChannelTokenData { openclaw_channel_token: string; [key: string]: unknown; } interface UpdateInfo { update_strategy: number; download_url?: string; version?: string; release_notes?: string; [key: string]: unknown; } interface DeviceInfo { [key: string]: unknown; } interface ContactLinkData { [key: string]: unknown; } declare class QClawClient { private env; private urls; private jwtToken; private userInfo; private webVersion; constructor(opts?: ClientOptions); get envUrls(): Readonly; get wxLoginConfig(): Readonly; get currentUser(): Readonly | null; get token(): string | null; /** Returns the login key (X-Token header), falls back to the hardcoded default. */ private get loginKey(); /** * Build the full URL for a gateway endpoint. */ private buildUrl; /** * Build the standard request headers expected by the jprx gateway. */ private buildHeaders; /** * Core request method. Every API call is a POST with JSON body that * includes `web_version` and `web_env` alongside caller-supplied params. * * Handles: * - Automatic JWT renewal via `X-New-Token` response header * - Session expiration (code 21004) → clears local auth state * - Nested Tencent response envelope unwrapping */ private request; /** * Unwrap the deeply-nested Tencent response envelope. * * Real responses have varying nesting depths, observed patterns: * { ret, data: { resp: { common, data: PAYLOAD } } } ← getWxLoginState * { ret, resp: { common, data: PAYLOAD } } ← some endpoints * { data: PAYLOAD } ← simple responses * * This method walks the known wrapper keys until it finds the innermost * `data` that doesn't itself contain another `resp` or `data` wrapper. */ private static unwrapData; /** * Get the WeChat OAuth QR-code login URL (for embedding in a webview / iframe). * The official SDK at https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js * normally renders this, but you can also construct it manually. */ buildWxLoginUrl(state: string): string; /** * Step 1 of login: obtain a CSRF `state` parameter for the QR login flow. * Endpoint: data/4050/forward */ getWxLoginState(params: { guid: string; }): Promise>; /** * Step 2 of login: exchange the WeChat authorization `code` for a session. * Returns a JWT (`token`) and an `openclaw_channel_token`. * Endpoint: data/4026/forward */ wxLogin(params: { guid: string; code: string; state: string; }): Promise>; /** * Fetch the currently logged-in user's profile. * Endpoint: data/4027/forward */ getUserInfo(params: { guid: string; }): Promise>; /** * Log out and invalidate the current session. * Endpoint: data/4028/forward */ wxLogout(params: { guid: string; }): Promise; /** * Create an API key for the qclaw model provider. * Endpoint: data/4055/forward */ createApiKey(): Promise>; /** * Check whether the current user has verified an invite code. * Endpoint: data/4056/forward */ checkInviteCode(params: { guid: string; }): Promise>; /** * Submit an invite code for verification. * Endpoint: data/4057/forward */ submitInviteCode(params: { guid: string; invite_code: string; }): Promise; /** * Refresh the `openclaw_channel_token` used by the wechat-access channel. * Endpoint: data/4058/forward */ refreshChannelToken(): Promise; /** * Check for app updates. * Endpoint: data/4066/forward */ checkUpdate(currentVersion?: string, systemType?: string): Promise>; /** * Generate a contact link (专属链接). * Endpoint: data/4018/forward */ generateContactLink(params: Record): Promise>; /** * Query device status by GUID. * Endpoint: data/4019/forward */ queryDeviceByGuid(params: Record): Promise>; /** * Disconnect a device. * Endpoint: data/4020/forward */ disconnectDevice(params: Record): Promise; /** * Build the config patch object that the Electron app writes via IPC * after a successful login. This is what goes into the OpenClaw * gateway's YAML/JSON config file. */ buildConfigPatch(channelToken: string | null, apiKey: string | null): Record; /** * Convenience: run the full post-login config update sequence * (create API key + build config patch). */ buildPostLoginConfig(channelToken: string): Promise>; /** Get environment URLs without instantiating a client. */ static getEnvUrls(env: Environment): Readonly; /** Get WeChat login config without instantiating a client. */ static getWxLoginConfig(env: Environment): Readonly; /** All known endpoint paths. */ static readonly Endpoints: { readonly GENERATE_CONTACT_LINK: "data/4018/forward"; readonly QUERY_DEVICE_BY_GUID: "data/4019/forward"; readonly DISCONNECT_DEVICE: "data/4020/forward"; readonly WX_LOGIN: "data/4026/forward"; readonly GET_USER_INFO: "data/4027/forward"; readonly WX_LOGOUT: "data/4028/forward"; readonly GET_WX_LOGIN_STATE: "data/4050/forward"; readonly CREATE_API_KEY: "data/4055/forward"; readonly CHECK_INVITE_CODE: "data/4056/forward"; readonly SUBMIT_INVITE_CODE: "data/4057/forward"; readonly REFRESH_CHANNEL_TOKEN: "data/4058/forward"; readonly CHECK_UPDATE: "data/4066/forward"; }; /** Unwrap a Tencent-style nested response. */ static unwrap(res: ApiResponse): T | null; } //#endregion export { AGPClient, type AGPClientCallbacks, type AGPClientConfig, type AGPEnvelope, type AGPMethod, ApiKeyData, ApiResponse, type CancelMessage, type CancelPayload, ChannelTokenData, ClientOptions, type ConnectionState, ContactLinkData, type ContentBlock, DeviceInfo, EnvUrls, Environment, InviteCodeStatus, type PromptMessage, type PromptPayload, type PromptResponseMessage, type PromptResponsePayload, QClawClient, QClawClient as default, type StopReason, type ToolCall, type ToolCallKind, type ToolCallStatus, type ToolLocation, UpdateInfo, type UpdateMessage, type UpdatePayload, type UpdateType, UserInfo, UserInfoData, WxLoginConfig, WxLoginData, WxLoginStateData, WxLoginUserInfo }; //# sourceMappingURL=index.d.mts.map