@dcrays/dcgchat-test 0.2.34 → 0.3.1

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/src/transport.ts CHANGED
@@ -1,19 +1,22 @@
1
1
  import { getWsConnection } from './utils/global.js'
2
2
  import { dcgLogger } from './utils/log.js'
3
+ import type { IMsgParams } from './types.js'
4
+ import { getEffectiveMsgParams, getParamsDefaults } from './utils/params.js'
3
5
 
4
- export type DcgchatMsgContext = {
5
- userId: number
6
- botToken: string
7
- domainId?: string
8
- appId?: string
9
- botId?: string
10
- agentId?: string
11
- sessionId: string
12
- messageId: string
6
+ /** sessionKey map 取参,再合并 overrides(channel 出站、媒体等) */
7
+ export function mergeSessionParams(sessionKey: string, overrides?: Partial<IMsgParams>): IMsgParams {
8
+ const base = getEffectiveMsgParams(sessionKey)
9
+ if (!overrides) return base
10
+ return { ...base, ...overrides }
11
+ }
12
+ export function mergeDefaultParams(overrides?: Partial<IMsgParams>): IMsgParams {
13
+ const base = getParamsDefaults()
14
+ if (!overrides) return base
15
+ return { ...base, ...overrides }
13
16
  }
14
17
 
15
- export function createMsgContext(msg: {
16
- _userId: number
18
+ export type InboundMsgForContext = {
19
+ _userId: number | string
17
20
  content: {
18
21
  bot_token: string
19
22
  domain_id?: string
@@ -23,86 +26,176 @@ export function createMsgContext(msg: {
23
26
  session_id: string
24
27
  message_id: string
25
28
  }
26
- }): DcgchatMsgContext {
29
+ }
30
+
31
+ export type OpenclawBotChatEnvelope = {
32
+ messageType: 'openclaw_bot_chat'
33
+ _userId: number | undefined
34
+ source: 'client'
35
+ content: Record<string, unknown>
36
+ }
37
+
38
+ function isInboundWire(arg: unknown): arg is InboundMsgForContext {
39
+ return Boolean(arg && typeof arg === 'object' && '_userId' in arg && 'content' in arg)
40
+ }
41
+
42
+ /** 下行 WebSocket 帧 → 内部上下文(字段缺省用 channel 配置补) */
43
+ function inboundToCtx(msg: InboundMsgForContext, d: IMsgParams): IMsgParams {
44
+ const c = msg.content
27
45
  return {
28
- userId: msg._userId,
29
- botToken: msg.content.bot_token,
30
- domainId: msg.content.domain_id,
31
- appId: msg.content.app_id,
32
- botId: msg.content.bot_id,
33
- agentId: msg.content.agent_id,
34
- sessionId: msg.content.session_id,
35
- messageId: msg.content.message_id
46
+ userId: Number(msg._userId ?? d.userId),
47
+ botToken: c.bot_token ?? d.botToken,
48
+ domainId: String(c.domain_id ?? d.domainId),
49
+ appId: String(c.app_id ?? d.appId),
50
+ botId: c.bot_id,
51
+ agentId: c.agent_id,
52
+ sessionId: c.session_id,
53
+ messageId: c.message_id
36
54
  }
37
55
  }
38
56
 
39
- function buildContent(ctx: DcgchatMsgContext, extra: Record<string, unknown>) {
57
+ /** 上行:与配置合并缺省后再 `...ctx` 覆盖(原 wsSendRaw) */
58
+ function mergeOutboundWithDefaults(ctx: IMsgParams, d: IMsgParams): IMsgParams {
59
+ return {
60
+ userId: Number(ctx.userId ?? d.userId),
61
+ botToken: ctx.botToken ?? d.botToken,
62
+ domainId: String(ctx.domainId ?? d.domainId),
63
+ appId: String(ctx.appId ?? d.appId),
64
+ ...ctx
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 组装完整 wire `content` 对象:先写会话/机器人基础字段(回落到 d),再合并调用方传入的 payload。
70
+ * `content` 在使用处构造(如 response、state、files),同名键可覆盖基础字段。
71
+ */
72
+ export function buildWireContent(base: IMsgParams, d: IMsgParams, content: Record<string, unknown>): Record<string, unknown> {
73
+ const resolvedBotToken = base.botToken ?? d.botToken
74
+ const domain = base.domainId ?? d.domainId
75
+ const app = base.appId ?? d.appId
40
76
  return {
41
- bot_token: ctx.botToken,
42
- domain_id: ctx.domainId,
43
- app_id: ctx.appId,
44
- bot_id: ctx.botId,
45
- agent_id: ctx.agentId,
46
- session_id: ctx.sessionId,
47
- message_id: ctx.messageId || Date.now().toString(),
48
- ...extra
77
+ bot_token: base.botToken || resolvedBotToken,
78
+ domain_id: base.domainId || domain,
79
+ app_id: base.appId || app,
80
+ bot_id: base.botId,
81
+ agent_id: base.agentId,
82
+ session_id: base.sessionId,
83
+ message_id: base.messageId || Date.now().toString(),
84
+ ...content
49
85
  }
50
86
  }
51
87
 
52
- function buildEnvelope(ctx: DcgchatMsgContext, extra: Record<string, unknown>) {
88
+ /** 上行:在已合并的 ctx 上套 openclaw_bot_chat 信封(messageType / _userId / source + content) */
89
+ function buildOutboundOpenclawBotChatEnvelope(
90
+ ctx: IMsgParams,
91
+ content: Record<string, unknown>,
92
+ opts?: { mergeChannelDefaults?: boolean }
93
+ ): OpenclawBotChatEnvelope {
94
+ const d = getParamsDefaults()
95
+ const base = opts?.mergeChannelDefaults ? mergeOutboundWithDefaults(ctx, d) : ctx
53
96
  return {
54
- messageType: 'openclaw_bot_chat' as const,
55
- _userId: ctx.userId,
56
- source: 'client' as const,
57
- content: buildContent(ctx, extra)
97
+ messageType: 'openclaw_bot_chat',
98
+ _userId: base.userId,
99
+ source: 'client',
100
+ content: buildWireContent(base, d, content)
58
101
  }
59
102
  }
60
103
 
104
+ /**
105
+ * 下行解析为 DcgchatMsgContext,或上行组装 openclaw_bot_chat 信封。
106
+ * 上行时 `content` 由调用方传入;基础参数来自 `ctx` 与 `getParamsDefaults()`(可选 mergeChannelDefaults,同原 wsSendRaw)。
107
+ */
108
+ export function buildOpenclawBotChat(msg: InboundMsgForContext): IMsgParams
109
+ export function buildOpenclawBotChat(
110
+ ctx: IMsgParams,
111
+ content: Record<string, unknown>,
112
+ opts?: { mergeChannelDefaults?: boolean }
113
+ ): OpenclawBotChatEnvelope
114
+ export function buildOpenclawBotChat(
115
+ arg1: InboundMsgForContext | IMsgParams,
116
+ arg2?: Record<string, unknown>,
117
+ opts?: { mergeChannelDefaults?: boolean }
118
+ ): IMsgParams | OpenclawBotChatEnvelope {
119
+ const d = getParamsDefaults()
120
+
121
+ if (arg2 === undefined && isInboundWire(arg1)) {
122
+ return inboundToCtx(arg1, d)
123
+ }
124
+
125
+ const ctx = arg1 as IMsgParams
126
+ return buildOutboundOpenclawBotChatEnvelope(ctx, arg2 ?? {}, opts)
127
+ }
128
+
61
129
  export function isWsOpen(): boolean {
62
130
  const isOpen = getWsConnection()?.readyState === WebSocket.OPEN
63
131
  if (!isOpen) {
64
- dcgLogger(`socket not ready ${getWsConnection()?.readyState}`, 'error')
132
+ dcgLogger(`server socket not ready ${getWsConnection()?.readyState}`, 'error')
65
133
  }
66
134
  return isOpen
67
135
  }
68
136
 
69
137
  /**
70
- * Content is stringified separately (double-encoded) to match the
71
- * dcgchat wire protocol used by the chat stream path.
138
+ * 聊天流路径:content 单独 JSON.stringify(双重编码),符合 dcgchat 协议。
139
+ * `ctx` 须由调用方用 getEffectiveMsgParams(sessionKey) 等解析好;`content` 为完整业务 payload。
72
140
  */
73
- export function wsSend(ctx: DcgchatMsgContext, extra: Record<string, unknown>): boolean {
141
+ export function wsSend(ctx: IMsgParams, content: Record<string, unknown>): boolean {
74
142
  const ws = getWsConnection()
75
143
  if (ws?.readyState !== WebSocket.OPEN) return false
76
- const envelope = buildEnvelope(ctx, extra)
144
+ const envelope = buildOpenclawBotChat(ctx, content)
77
145
  ws.send(JSON.stringify({ ...envelope, content: JSON.stringify(envelope.content) }))
78
146
  return true
79
147
  }
80
148
 
81
149
  /**
82
- * Content stays as a nested object (single-encoded).
83
- * Matches the legacy wire format used by media and outbound-pipeline messages.
150
+ * 媒体 / channel 出站:content 保持嵌套对象(单次编码)。
151
+ * `ctx` 须由调用方解析(如需合并覆盖可先 mergeSessionParams)。
84
152
  */
85
- export function wsSendRaw(ctx: DcgchatMsgContext, extra: Record<string, unknown>): boolean {
153
+ export function wsSendRaw(ctx: IMsgParams, content: Record<string, unknown>): boolean {
86
154
  const ws = getWsConnection()
87
155
  if (isWsOpen()) {
88
- ws?.send(JSON.stringify(buildEnvelope(ctx, extra)))
156
+ ws?.send(JSON.stringify(buildOpenclawBotChat(ctx, content, { mergeChannelDefaults: true })))
89
157
  }
90
158
  return true
91
159
  }
92
160
 
93
- export function sendChunk(ctx: DcgchatMsgContext, text: string): boolean {
161
+ export function sendChunk(text: string, ctx: IMsgParams): boolean {
94
162
  return wsSend(ctx, { response: text, state: 'chunk' })
95
163
  }
96
164
 
97
- export function sendFinal(ctx: DcgchatMsgContext): boolean {
165
+ export function sendFinal(ctx: IMsgParams): boolean {
98
166
  dcgLogger(` message handling complete state: final`)
99
167
  return wsSend(ctx, { response: '', state: 'final' })
100
168
  }
101
169
 
102
- export function sendText(ctx: DcgchatMsgContext, text: string, event?: Record<string, unknown>): boolean {
170
+ export function sendText(text: string, ctx: IMsgParams, event?: Record<string, unknown>): boolean {
103
171
  return wsSend(ctx, { response: text, ...event })
104
172
  }
105
173
 
106
- export function sendError(ctx: DcgchatMsgContext, errorMsg: string): boolean {
174
+ export function sendError(errorMsg: string, ctx: IMsgParams): boolean {
107
175
  return wsSend(ctx, { response: `[错误] ${errorMsg}`, state: 'final' })
108
176
  }
177
+
178
+ export function sendEventMessage(url: string, sessionKey: string) {
179
+ const ctx = getEffectiveMsgParams(sessionKey)
180
+ const ws = getWsConnection()
181
+ if (isWsOpen()) {
182
+ ws?.send(
183
+ JSON.stringify({
184
+ messageType: 'openclaw_bot_event',
185
+ source: 'client',
186
+ content: {
187
+ event_type: 'cron',
188
+ operation_type: 'install',
189
+ bot_token: ctx.botToken,
190
+ domain_id: ctx.domainId,
191
+ app_id: ctx.appId,
192
+ oss_url: url,
193
+ bot_id: ctx.botId,
194
+ agent_id: ctx.agentId,
195
+ session_id: ctx.sessionId,
196
+ message_id: ctx.messageId || Date.now().toString()
197
+ }
198
+ })
199
+ )
200
+ }
201
+ }
package/src/types.ts CHANGED
@@ -44,6 +44,7 @@ export type InboundMessage = {
44
44
  bot_id?: string
45
45
  agent_id?: string
46
46
  session_id: string
47
+ real_mobook: string | number
47
48
  message_id: string
48
49
  text: string
49
50
  files?: {
@@ -115,12 +116,15 @@ export interface IStsTokenReq {
115
116
  }
116
117
 
117
118
  export interface IMsgParams {
118
- userId: number
119
- token: string
120
- sessionId: string
121
- messageId: string
122
- domainId: string
123
- appId: string
124
- botId: string
125
- agentId: string
119
+ userId?: number
120
+ botToken?: string
121
+ sessionId?: string
122
+ messageId?: string
123
+ domainId?: string
124
+ appId?: string
125
+ botId?: string
126
+ agentId?: string
127
+ /** 与 OpenClaw 路由一致,用于 map 与异步链路(工具 / HTTP / cron)对齐当前会话 */
128
+ sessionKey?: string
129
+ real_mobook?: string | number
126
130
  }
@@ -24,7 +24,6 @@ export function getOpenClawConfig(): OpenClawConfig | null {
24
24
 
25
25
  import type { OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk'
26
26
  import { dcgLogger } from './log.js'
27
- import { IMsgParams } from '../types.js'
28
27
  import { channelInfo, ENV } from './constant.js'
29
28
 
30
29
  const path = require('path')
@@ -69,14 +68,6 @@ export function getDcgchatRuntime(): PluginRuntime {
69
68
  return runtime as PluginRuntime
70
69
  }
71
70
 
72
- let msgParams = {} as IMsgParams
73
- export function setMsgParams(params: any) {
74
- msgParams = params
75
- }
76
- export function getMsgParams() {
77
- return msgParams
78
- }
79
-
80
71
  let msgStatus: 'running' | 'finished' | '' = ''
81
72
  export function setMsgStatus(status: 'running' | 'finished' | '') {
82
73
  msgStatus = status
@@ -0,0 +1,65 @@
1
+ import { channelInfo, ENV } from './constant.js'
2
+ import { getOpenClawConfig } from './global.js'
3
+ import type { DcgchatConfig, IMsgParams } from '../types.js'
4
+
5
+ /**
6
+ * map key 是 session_key,value 为该会话下已 merge 后的消息参数。
7
+ */
8
+ const paramsMessageMap = new Map<string, IMsgParams>()
9
+
10
+ /** 最近一次 setParamsMessage 的 key,供不传参的 getEffectiveMsgParams() 使用 */
11
+ let currentSessionKey: string | null = null
12
+
13
+ /** 从 OpenClaw 配置读取当前 channel 的基础参数(唯一来源,供 transport / resolve 等复用) */
14
+ export function getParamsDefaults(): IMsgParams {
15
+ const ch = (getOpenClawConfig()?.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {}
16
+ return {
17
+ userId: Number(ch.userId ?? 0),
18
+ botToken: ch.botToken ?? '',
19
+ sessionId: '',
20
+ messageId: '',
21
+ domainId: String(ch.domainId ?? '1000'),
22
+ appId: String(ch.appId ?? '100'),
23
+ botId: '',
24
+ agentId: '',
25
+ sessionKey: ''
26
+ }
27
+ }
28
+
29
+ export function resolveParamsMessage(params: Partial<IMsgParams>): IMsgParams {
30
+ const defaults = getParamsDefaults()
31
+ return {
32
+ userId: Number(params.userId ?? defaults.userId),
33
+ botToken: params.botToken ?? defaults.botToken,
34
+ sessionId: params.sessionId ?? defaults.sessionId,
35
+ messageId: params.messageId ?? defaults.messageId,
36
+ domainId: String(params.domainId ?? defaults.domainId),
37
+ appId: String(params.appId ?? defaults.appId),
38
+ botId: params.botId ?? defaults.botId,
39
+ agentId: params.agentId ?? defaults.agentId,
40
+ sessionKey: params.sessionKey ?? defaults.sessionKey
41
+ }
42
+ }
43
+
44
+ /**
45
+ * 统一取值入口:显式 sessionKey,或回落到当前会话;再与配置缺省 merge,保证字段完整。
46
+ */
47
+ export function getEffectiveMsgParams(sessionKey?: string): IMsgParams {
48
+ const key = sessionKey ?? currentSessionKey
49
+ const stored = key ? paramsMessageMap.get(key) : undefined
50
+ return stored ? resolveParamsMessage(stored) : getParamsDefaults()
51
+ }
52
+
53
+ export function setParamsMessage(sessionKey: string, params: Partial<IMsgParams>) {
54
+ if (!sessionKey) return
55
+ currentSessionKey = sessionKey
56
+ paramsMessageMap.set(sessionKey, resolveParamsMessage({ ...params, sessionKey }))
57
+ }
58
+
59
+ export function getParamsMessage(sessionKey: string): IMsgParams | undefined {
60
+ return paramsMessageMap.get(sessionKey)
61
+ }
62
+
63
+ export function getCurrentSessionKey(): string | null {
64
+ return currentSessionKey
65
+ }