@tencent-weixin/openclaw-weixin 2.4.1 → 2.4.2
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/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/src/api/api.js +1 -2
- package/dist/src/api/api.js.map +1 -1
- package/dist/src/auth/accounts.js.map +1 -1
- package/dist/src/channel.js +11 -0
- package/dist/src/channel.js.map +1 -1
- package/dist/src/media/voice-outbound.js +177 -0
- package/dist/src/media/voice-outbound.js.map +1 -0
- package/dist/src/messaging/abort-fence.js +70 -0
- package/dist/src/messaging/abort-fence.js.map +1 -0
- package/dist/src/messaging/buttons.js +117 -0
- package/dist/src/messaging/buttons.js.map +1 -0
- package/dist/src/messaging/lane-key.js +66 -0
- package/dist/src/messaging/lane-key.js.map +1 -0
- package/dist/src/messaging/merged-record.js +149 -0
- package/dist/src/messaging/merged-record.js.map +1 -0
- package/dist/src/messaging/model-buttons.js +182 -0
- package/dist/src/messaging/model-buttons.js.map +1 -0
- package/dist/src/messaging/model-callback-handler.js +133 -0
- package/dist/src/messaging/model-callback-handler.js.map +1 -0
- package/dist/src/monitor/lane-scheduler.js +46 -0
- package/dist/src/monitor/lane-scheduler.js.map +1 -0
- package/dist/src/monitor/monitor.js +5 -12
- package/dist/src/monitor/monitor.js.map +1 -1
- package/dist/src/streaming/stream-pipeline.js +431 -0
- package/dist/src/streaming/stream-pipeline.js.map +1 -0
- package/dist/src/streaming/stream-session.js +260 -0
- package/dist/src/streaming/stream-session.js.map +1 -0
- package/dist/src/streaming/stream.js +239 -0
- package/dist/src/streaming/stream.js.map +1 -0
- package/dist/src/util/markdown-fences.js +54 -0
- package/dist/src/util/markdown-fences.js.map +1 -0
- package/index.ts +0 -5
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/api/api.ts +2 -3
- package/src/auth/accounts.ts +0 -1
- package/src/channel.ts +13 -1
- package/src/monitor/monitor.ts +11 -10
- package/src/runtime.ts +0 -70
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { StreamingMarkdownFilter } from "../messaging/markdown-filter.js";
|
|
2
|
+
import { findFenceSpanAt, parseFenceSpans } from "../util/markdown-fences.js";
|
|
3
|
+
/**
|
|
4
|
+
* Matches every line starting with `WEIXIN_BUTTONS:` in the text. Kept in
|
|
5
|
+
* sync with INLINE_BUTTONS_RE in messaging/buttons.ts so the streaming
|
|
6
|
+
* suppressor drops exactly what parseInlineButtons later strips at deliver
|
|
7
|
+
* — including the fence-aware skip (a marker inside a code block is treated
|
|
8
|
+
* as literal documentation, never suppressed and never stripped).
|
|
9
|
+
*/
|
|
10
|
+
const MARKER_LINE_START_RE = /^WEIXIN_BUTTONS:/gm;
|
|
11
|
+
function stripReasoningFormat(text) {
|
|
12
|
+
let s = text;
|
|
13
|
+
if (s.startsWith("Reasoning:\n"))
|
|
14
|
+
s = s.slice("Reasoning:\n".length);
|
|
15
|
+
else if (s.startsWith("Reasoning:"))
|
|
16
|
+
s = s.slice("Reasoning:".length);
|
|
17
|
+
else
|
|
18
|
+
return text;
|
|
19
|
+
return s.split("\n").map((line) => {
|
|
20
|
+
if (line.startsWith("_") && line.endsWith("_") && line.length >= 2) {
|
|
21
|
+
return line.slice(1, -1);
|
|
22
|
+
}
|
|
23
|
+
if (line.startsWith("_"))
|
|
24
|
+
return line.slice(1);
|
|
25
|
+
if (line.endsWith("_"))
|
|
26
|
+
return line.slice(0, -1);
|
|
27
|
+
return line;
|
|
28
|
+
}).join("\n");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Thin synchronous adapter that converts SDK callbacks into pipeline commands.
|
|
32
|
+
*
|
|
33
|
+
* Handles only text-processing concerns (delta extraction, markdown filtering,
|
|
34
|
+
* reasoning format stripping). All async protocol work is delegated to the
|
|
35
|
+
* StreamPipeline via `push()`.
|
|
36
|
+
*/
|
|
37
|
+
export class StreamSession {
|
|
38
|
+
pipeline;
|
|
39
|
+
log;
|
|
40
|
+
// -- text processing (synchronous) --
|
|
41
|
+
prevReasoningText = "";
|
|
42
|
+
prevPartialText = "";
|
|
43
|
+
thinkingMdFilter = new StreamingMarkdownFilter();
|
|
44
|
+
resultMdFilter = new StreamingMarkdownFilter();
|
|
45
|
+
resultFilteredAccum = "";
|
|
46
|
+
// -- WEIXIN_BUTTONS suppression for streaming --
|
|
47
|
+
_buttonsLineSuppressed = false;
|
|
48
|
+
// -- debug/logging --
|
|
49
|
+
thinkingSegments = [];
|
|
50
|
+
// -- deliver payload (managed locally, not a pipeline command) --
|
|
51
|
+
_lastDeliverPayload;
|
|
52
|
+
_finalTextCache;
|
|
53
|
+
// -- TEMP DEBUG: track MEDIA: directive presence in raw stream --
|
|
54
|
+
_mediaDebugSeenAt;
|
|
55
|
+
_mediaDebugFirstSnippet;
|
|
56
|
+
constructor(opts) {
|
|
57
|
+
this.pipeline = opts.pipeline;
|
|
58
|
+
this.log = opts.log;
|
|
59
|
+
}
|
|
60
|
+
/** Reply callbacks to spread into replyOptions. */
|
|
61
|
+
get replyCallbacks() {
|
|
62
|
+
return {
|
|
63
|
+
onReasoningStream: (p) => this.onReasoningStream(p),
|
|
64
|
+
onReasoningEnd: () => this.onReasoningEnd(),
|
|
65
|
+
onToolStart: (p) => this.onToolStart(p),
|
|
66
|
+
onAssistantMessageStart: () => this.onAssistantMessageStart(),
|
|
67
|
+
onPartialReply: (p) => this.onPartialReply(p),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
bufferDeliverPayload(payload) {
|
|
71
|
+
this._lastDeliverPayload = payload;
|
|
72
|
+
this._finalTextCache = undefined;
|
|
73
|
+
}
|
|
74
|
+
get lastDeliverPayload() { return this._lastDeliverPayload; }
|
|
75
|
+
get streamId() { return this.pipeline.resultStreamId; }
|
|
76
|
+
get hadStreaming() { return this.pipeline.hadStreaming; }
|
|
77
|
+
/**
|
|
78
|
+
* One-shot markdown-filtered version of the deliver text.
|
|
79
|
+
* Result is memoized; invalidated when bufferDeliverPayload is called.
|
|
80
|
+
*/
|
|
81
|
+
getFinalText() {
|
|
82
|
+
if (!this._lastDeliverPayload)
|
|
83
|
+
return "";
|
|
84
|
+
if (this._finalTextCache !== undefined)
|
|
85
|
+
return this._finalTextCache;
|
|
86
|
+
const f = new StreamingMarkdownFilter();
|
|
87
|
+
this._finalTextCache = f.feed(this._lastDeliverPayload.text) + f.flush();
|
|
88
|
+
return this._finalTextCache;
|
|
89
|
+
}
|
|
90
|
+
/** Flush filters, push finalize command, and wait for the pipeline to drain. */
|
|
91
|
+
async finalize() {
|
|
92
|
+
this.log.debug(`stream-cb: finalize hadDeliver=${this._lastDeliverPayload != null} resultAccumLen=${this.resultFilteredAccum.length} prevPartialLen=${this.prevPartialText.length}`);
|
|
93
|
+
// [MEDIA-DEBUG] Dump the tail of the raw accumulated text so we can compare
|
|
94
|
+
// against the deliver payload (which has MEDIA: stripped by the host).
|
|
95
|
+
{
|
|
96
|
+
const raw = this.prevPartialText;
|
|
97
|
+
const tail = raw.slice(-300).replaceAll("\n", "\\n");
|
|
98
|
+
const hasMedia = /\bmedia:/i.test(raw);
|
|
99
|
+
const allMediaMatches = hasMedia
|
|
100
|
+
? Array.from(raw.matchAll(/\bMEDIA:[^\n]*/gi)).map((m) => m[0])
|
|
101
|
+
: [];
|
|
102
|
+
this.log.info(`[media-debug] finalize rawLen=${raw.length} hasMediaInRaw=${hasMedia} mediaLines=${JSON.stringify(allMediaMatches)} tail="${tail}"`);
|
|
103
|
+
if (this._lastDeliverPayload) {
|
|
104
|
+
const dt = this._lastDeliverPayload.text ?? "";
|
|
105
|
+
this.log.info(`[media-debug] finalize deliver payload textLen=${dt.length} mediaUrl=${this._lastDeliverPayload.mediaUrl ?? "none"} deliverHasMedia=${/\bmedia:/i.test(dt)} deliverTail="${dt.slice(-300).replaceAll("\n", "\\n")}"`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this.log.info(`[media-debug] finalize no deliver payload buffered`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Thinking filter is flushed by onReasoningEnd(); we don't flush it again
|
|
112
|
+
// here to avoid pushing stale thinking text into a result-phase pipeline.
|
|
113
|
+
if (this.prevReasoningText) {
|
|
114
|
+
this.thinkingSegments.push({ type: "text", text: this.prevReasoningText });
|
|
115
|
+
this.prevReasoningText = "";
|
|
116
|
+
}
|
|
117
|
+
this.log.debug(`stream: thinking segments=${JSON.stringify(this.thinkingSegments)}`);
|
|
118
|
+
// Flush result markdown filter → push remaining as result text
|
|
119
|
+
const resultRemaining = this.resultMdFilter.flush();
|
|
120
|
+
if (resultRemaining) {
|
|
121
|
+
this.resultFilteredAccum += resultRemaining;
|
|
122
|
+
this.pipeline.push({ kind: "result", text: resultRemaining });
|
|
123
|
+
}
|
|
124
|
+
// Check for gap between streamed accum and one-shot filtered deliver text.
|
|
125
|
+
// Only fill the gap when some result text was already streamed — otherwise
|
|
126
|
+
// (e.g. synchronous command replies like /models) pushing a fresh "result"
|
|
127
|
+
// would force the pipeline to start a brand-new streaming phase for a
|
|
128
|
+
// single buffered payload, costing ~2s of extra HTTP round-trips. In the
|
|
129
|
+
// thinking-only → deliver case the pipeline's doFinalize handles the
|
|
130
|
+
// late thinking→result transition via its own _hadStreaming branch.
|
|
131
|
+
if (this._lastDeliverPayload && this.resultFilteredAccum.length > 0) {
|
|
132
|
+
const fullFiltered = this.getFinalText();
|
|
133
|
+
if (fullFiltered.length > this.resultFilteredAccum.length &&
|
|
134
|
+
fullFiltered.startsWith(this.resultFilteredAccum)) {
|
|
135
|
+
const gap = fullFiltered.slice(this.resultFilteredAccum.length);
|
|
136
|
+
this.pipeline.push({ kind: "result", text: gap });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
this.pipeline.push({
|
|
140
|
+
kind: "finalize",
|
|
141
|
+
deliverText: this.getFinalText() || undefined,
|
|
142
|
+
});
|
|
143
|
+
await this.pipeline.drain();
|
|
144
|
+
}
|
|
145
|
+
async abort(errorMsg) {
|
|
146
|
+
await this.pipeline.abort(errorMsg);
|
|
147
|
+
}
|
|
148
|
+
// ---- SDK callbacks (all synchronous, just push commands) ----
|
|
149
|
+
onReasoningStream(payload) {
|
|
150
|
+
const rawText = payload.text ?? "";
|
|
151
|
+
const stripped = stripReasoningFormat(rawText);
|
|
152
|
+
if (stripped === this.prevReasoningText)
|
|
153
|
+
return;
|
|
154
|
+
const isRefresh = !stripped.startsWith(this.prevReasoningText);
|
|
155
|
+
const delta = isRefresh ? stripped : stripped.slice(this.prevReasoningText.length);
|
|
156
|
+
this.prevReasoningText = stripped;
|
|
157
|
+
if (!delta)
|
|
158
|
+
return;
|
|
159
|
+
if (isRefresh) {
|
|
160
|
+
this.thinkingMdFilter = new StreamingMarkdownFilter();
|
|
161
|
+
}
|
|
162
|
+
const filtered = this.thinkingMdFilter.feed(delta);
|
|
163
|
+
if (!filtered)
|
|
164
|
+
return;
|
|
165
|
+
if (isRefresh) {
|
|
166
|
+
this.pipeline.push({ kind: "thinking_refresh", text: filtered });
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.pipeline.push({ kind: "thinking", text: filtered });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
onReasoningEnd() {
|
|
173
|
+
this.log.debug(`stream-cb: onReasoningEnd`);
|
|
174
|
+
const remaining = this.thinkingMdFilter.flush();
|
|
175
|
+
if (remaining) {
|
|
176
|
+
this.pipeline.push({ kind: "thinking", text: remaining });
|
|
177
|
+
}
|
|
178
|
+
if (this.prevReasoningText) {
|
|
179
|
+
this.thinkingSegments.push({ type: "text", text: this.prevReasoningText });
|
|
180
|
+
this.prevReasoningText = "";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
onToolStart(payload) {
|
|
184
|
+
this.log.debug(`stream-cb: onToolStart name=${payload.name ?? "?"} phase=${payload.phase ?? "?"}`);
|
|
185
|
+
const phase = payload.phase === "complete" ? "end"
|
|
186
|
+
: payload.phase === "running" ? "continue"
|
|
187
|
+
: "start";
|
|
188
|
+
if (phase === "start") {
|
|
189
|
+
this.thinkingSegments.push({ type: "tool_calling", name: payload.name });
|
|
190
|
+
}
|
|
191
|
+
this.pipeline.push({ kind: "tool_call", name: payload.name, phase: phase });
|
|
192
|
+
}
|
|
193
|
+
onAssistantMessageStart() {
|
|
194
|
+
this.log.debug(`stream-cb: onAssistantMessageStart`);
|
|
195
|
+
this.prevPartialText = "";
|
|
196
|
+
this.resultFilteredAccum = "";
|
|
197
|
+
this.resultMdFilter = new StreamingMarkdownFilter();
|
|
198
|
+
this._buttonsLineSuppressed = false;
|
|
199
|
+
}
|
|
200
|
+
onPartialReply(payload) {
|
|
201
|
+
const fullText = payload.text ?? "";
|
|
202
|
+
if (this._buttonsLineSuppressed) {
|
|
203
|
+
this.prevPartialText = fullText;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const prevLen = this.prevPartialText.length;
|
|
207
|
+
const rawDelta = fullText.slice(prevLen);
|
|
208
|
+
this.prevPartialText = fullText;
|
|
209
|
+
if (!rawDelta)
|
|
210
|
+
return;
|
|
211
|
+
// [MEDIA-DEBUG] Detect MEDIA: directive appearance in raw stream so we can
|
|
212
|
+
// verify whether the host strips it before deliver. Logs once per stream.
|
|
213
|
+
if (this._mediaDebugSeenAt === undefined && /\bmedia:/i.test(fullText)) {
|
|
214
|
+
this._mediaDebugSeenAt = fullText.search(/\bmedia:/i);
|
|
215
|
+
const start = Math.max(0, this._mediaDebugSeenAt - 40);
|
|
216
|
+
const end = Math.min(fullText.length, this._mediaDebugSeenAt + 200);
|
|
217
|
+
this._mediaDebugFirstSnippet = fullText.slice(start, end).replaceAll("\n", "\\n");
|
|
218
|
+
this.log.info(`[media-debug] MEDIA: seen in raw stream at offset=${this._mediaDebugSeenAt} fullLen=${fullText.length} snippet="${this._mediaDebugFirstSnippet}"`);
|
|
219
|
+
}
|
|
220
|
+
// Detect WEIXIN_BUTTONS: marker anywhere in the accumulated text. We mirror
|
|
221
|
+
// the recognition rule used by parseInlineButtons (/^WEIXIN_BUTTONS:/m —
|
|
222
|
+
// start of any line, fence-aware), so whatever gets stripped at deliver-time
|
|
223
|
+
// also gets suppressed in the live stream, regardless of whether the marker
|
|
224
|
+
// is at the tail, followed by trailing newline, or followed by more content.
|
|
225
|
+
// Markers inside an (open or closed) markdown code fence are treated as
|
|
226
|
+
// literal documentation and never suppressed.
|
|
227
|
+
const fenceSpans = parseFenceSpans(fullText);
|
|
228
|
+
const re = new RegExp(MARKER_LINE_START_RE.source, MARKER_LINE_START_RE.flags);
|
|
229
|
+
let markerStart = -1;
|
|
230
|
+
let m;
|
|
231
|
+
while ((m = re.exec(fullText)) !== null) {
|
|
232
|
+
if (!findFenceSpanAt(fenceSpans, m.index)) {
|
|
233
|
+
markerStart = m.index;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (markerStart >= 0) {
|
|
238
|
+
this._buttonsLineSuppressed = true;
|
|
239
|
+
// Push the portion of the delta that lies BEFORE the marker start.
|
|
240
|
+
// Anything from markerStart onwards (marker itself + any future tokens)
|
|
241
|
+
// is dropped from the stream.
|
|
242
|
+
const deltaCutoff = Math.max(0, markerStart - prevLen);
|
|
243
|
+
const deltaBeforeMarker = rawDelta.slice(0, deltaCutoff);
|
|
244
|
+
if (deltaBeforeMarker) {
|
|
245
|
+
const filteredDelta = this.resultMdFilter.feed(deltaBeforeMarker);
|
|
246
|
+
if (filteredDelta) {
|
|
247
|
+
this.resultFilteredAccum += filteredDelta;
|
|
248
|
+
this.pipeline.push({ kind: "result", text: filteredDelta });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const filteredDelta = this.resultMdFilter.feed(rawDelta);
|
|
254
|
+
if (filteredDelta) {
|
|
255
|
+
this.resultFilteredAccum += filteredDelta;
|
|
256
|
+
this.pipeline.push({ kind: "result", text: filteredDelta });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=stream-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-session.js","sourceRoot":"","sources":["../../../src/streaming/stream-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAG9E;;;;;;GAMG;AACH,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAelD,SAAS,oBAAoB,CAAC,IAAY;IACxC,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,IAAI,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;SAChE,IAAI,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;;QACjE,OAAO,IAAI,CAAC;IACjB,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAChC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACnE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/C,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IACP,QAAQ,CAAiB;IACzB,GAAG,CAAY;IAEhC,sCAAsC;IAC9B,iBAAiB,GAAG,EAAE,CAAC;IACvB,eAAe,GAAG,EAAE,CAAC;IACrB,gBAAgB,GAAG,IAAI,uBAAuB,EAAE,CAAC;IACjD,cAAc,GAAG,IAAI,uBAAuB,EAAE,CAAC;IAC/C,mBAAmB,GAAG,EAAE,CAAC;IAEjC,iDAAiD;IACzC,sBAAsB,GAAG,KAAK,CAAC;IAEvC,sBAAsB;IACd,gBAAgB,GAAsB,EAAE,CAAC;IAEjD,kEAAkE;IAC1D,mBAAmB,CAAkD;IACrE,eAAe,CAAqB;IAE5C,kEAAkE;IAC1D,iBAAiB,CAAqB;IACtC,uBAAuB,CAAqB;IAEpD,YAAY,IAA0B;QACpC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;IACtB,CAAC;IAED,mDAAmD;IACnD,IAAI,cAAc;QAChB,OAAO;YACL,iBAAiB,EAAE,CAAC,CAAoB,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;YACtE,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE;YAC3C,WAAW,EAAE,CAAC,CAAoC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;YAC1E,uBAAuB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,uBAAuB,EAAE;YAC7D,cAAc,EAAE,CAAC,CAAoB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;SACjE,CAAC;IACJ,CAAC;IAED,oBAAoB,CAAC,OAA4C;QAC/D,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC;QACnC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;IACnC,CAAC;IAED,IAAI,kBAAkB,KAAK,OAAO,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC7D,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;IACvD,IAAI,YAAY,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;IAEzD;;;OAGG;IACH,YAAY;QACV,IAAI,CAAC,IAAI,CAAC,mBAAmB;YAAE,OAAO,EAAE,CAAC;QACzC,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,eAAe,CAAC;QACpE,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC;QACzE,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,kCAAkC,IAAI,CAAC,mBAAmB,IAAI,IAAI,mBAAmB,IAAI,CAAC,mBAAmB,CAAC,MAAM,mBAAmB,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CACrK,CAAC;QAEF,4EAA4E;QAC5E,uEAAuE;QACvE,CAAC;YACC,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC;YACjC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACrD,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACvC,MAAM,eAAe,GAAG,QAAQ;gBAC9B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/D,CAAC,CAAC,EAAE,CAAC;YACP,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,iCAAiC,GAAG,CAAC,MAAM,kBAAkB,QAAQ,eAAe,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,UAAU,IAAI,GAAG,CACrI,CAAC;YACF,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBAC7B,MAAM,EAAE,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC/C,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,kDAAkD,EAAE,CAAC,MAAM,aAAa,IAAI,CAAC,mBAAmB,CAAC,QAAQ,IAAI,MAAM,oBAAoB,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CACtN,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;QACD,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;YAC3E,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,6BAA6B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAErF,+DAA+D;QAC/D,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QACpD,IAAI,eAAe,EAAE,CAAC;YACpB,IAAI,CAAC,mBAAmB,IAAI,eAAe,CAAC;YAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3E,sEAAsE;QACtE,0EAA0E;QAC1E,qEAAqE;QACrE,oEAAoE;QACpE,IAAI,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpE,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YACzC,IACE,YAAY,CAAC,MAAM,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM;gBACrD,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,EACjD,CAAC;gBACD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;gBAChE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACjB,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,SAAS;SAC9C,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,QAAiB;QAC3B,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED,gEAAgE;IAExD,iBAAiB,CAAC,OAA0B;QAClD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,QAAQ,KAAK,IAAI,CAAC,iBAAiB;YAAE,OAAO;QAEhD,MAAM,SAAS,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAEnF,IAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC,gBAAgB,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxD,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAEO,cAAc;QACpB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAChD,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;YAC3E,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;IAEO,WAAW,CAAC,OAA0C;QAC5D,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,+BAA+B,OAAO,CAAC,IAAI,IAAI,GAAG,UAAU,OAAO,CAAC,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;QACnG,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK;YAChD,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU;gBAC1C,CAAC,CAAC,OAAO,CAAC;QACZ,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,KAAqC,EAAE,CAAC,CAAC;IAC9G,CAAC;IAEO,uBAAuB;QAC7B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACrD,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,cAAc,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACpD,IAAI,CAAC,sBAAsB,GAAG,KAAK,CAAC;IACtC,CAAC;IAEO,cAAc,CAAC,OAA0B;QAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAChC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC;YAChC,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;QAC5C,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC;QAChC,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,2EAA2E;QAC3E,0EAA0E;QAC1E,IAAI,IAAI,CAAC,iBAAiB,KAAK,SAAS,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvE,IAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YACtD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;YACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,GAAG,GAAG,CAAC,CAAC;YACpE,IAAI,CAAC,uBAAuB,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAClF,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,qDAAqD,IAAI,CAAC,iBAAiB,YAAY,QAAQ,CAAC,MAAM,aAAa,IAAI,CAAC,uBAAuB,GAAG,CACnJ,CAAC;QACJ,CAAC;QAED,4EAA4E;QAC5E,yEAAyE;QACzE,6EAA6E;QAC7E,4EAA4E;QAC5E,6EAA6E;QAC7E,wEAAwE;QACxE,8CAA8C;QAC9C,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC/E,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC;QACrB,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACxC,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1C,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC;gBACtB,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC;YACnC,mEAAmE;YACnE,wEAAwE;YACxE,8BAA8B;YAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC;YACvD,MAAM,iBAAiB,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;YACzD,IAAI,iBAAiB,EAAE,CAAC;gBACtB,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;gBAClE,IAAI,aAAa,EAAE,CAAC;oBAClB,IAAI,CAAC,mBAAmB,IAAI,aAAa,CAAC;oBAC1C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;gBAC9D,CAAC;YACH,CAAC;YACD,OAAO;QACT,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzD,IAAI,aAAa,EAAE,CAAC;YAClB,IAAI,CAAC,mBAAmB,IAAI,aAAa,CAAC;YAC1C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { initStream, syncStream } from "../api/api.js";
|
|
3
|
+
import { AbortType } from "../api/types.js";
|
|
4
|
+
import { getOrCreateDeviceId } from "../auth/accounts.js";
|
|
5
|
+
import { logger } from "../util/logger.js";
|
|
6
|
+
const STREAM_BUSINESS_TYPE = 10;
|
|
7
|
+
/**
|
|
8
|
+
* Manages a single uplink stream session.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const sender = new WeixinStreamSender(opts);
|
|
12
|
+
* await sender.init();
|
|
13
|
+
* await sender.sendPiece({ type: "text", text: "Hello " });
|
|
14
|
+
* await sender.sendPiece({ type: "text", text: "world!" });
|
|
15
|
+
* await sender.end();
|
|
16
|
+
*/
|
|
17
|
+
export class WeixinStreamSender {
|
|
18
|
+
opts;
|
|
19
|
+
deviceId;
|
|
20
|
+
clientStreamId;
|
|
21
|
+
streamTicket;
|
|
22
|
+
pieceSeq = 0;
|
|
23
|
+
ended = false;
|
|
24
|
+
/**
|
|
25
|
+
* Pieces from a previous sendPiece/end call that failed to reach the server.
|
|
26
|
+
* They are prepended to the next syncStream request so the server receives
|
|
27
|
+
* them with the original piece_seq values (true retry, not accumulation).
|
|
28
|
+
*/
|
|
29
|
+
pendingPieces = [];
|
|
30
|
+
/** pieceSeq value before the pending pieces were assigned; used for rollback. */
|
|
31
|
+
seqBeforePending = 0;
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
this.opts = opts;
|
|
34
|
+
this.deviceId = getOrCreateDeviceId(opts.accountId);
|
|
35
|
+
this.clientStreamId = `${opts.accountId}:${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
36
|
+
}
|
|
37
|
+
/** Call native_init_stream to obtain a stream_ticket. Must be called before sendPiece. */
|
|
38
|
+
async init() {
|
|
39
|
+
let resp;
|
|
40
|
+
try {
|
|
41
|
+
resp = await initStream({
|
|
42
|
+
baseUrl: this.opts.baseUrl,
|
|
43
|
+
token: this.opts.token,
|
|
44
|
+
timeoutMs: this.opts.timeoutMs,
|
|
45
|
+
accountId: this.opts.accountId,
|
|
46
|
+
body: {
|
|
47
|
+
device_id: this.deviceId,
|
|
48
|
+
client_stream_id: this.clientStreamId,
|
|
49
|
+
business_type: STREAM_BUSINESS_TYPE,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
logger.warn(`WeixinStreamSender.init: initStream threw streamId=${this.clientStreamId} err=${String(err)}`);
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
if (resp.base_response?.ret && resp.base_response.ret !== 0) {
|
|
58
|
+
logger.warn(`WeixinStreamSender.init: ret=${resp.base_response.ret} errmsg=${resp.base_response.errmsg ?? ""} streamId=${this.clientStreamId}`);
|
|
59
|
+
throw new Error(`initStream failed: ret=${resp.base_response.ret} errmsg=${resp.base_response.errmsg ?? ""}`);
|
|
60
|
+
}
|
|
61
|
+
if (!resp.stream_ticket) {
|
|
62
|
+
logger.warn(`WeixinStreamSender.init: no stream_ticket in response streamId=${this.clientStreamId}`);
|
|
63
|
+
throw new Error("initStream: no stream_ticket in response");
|
|
64
|
+
}
|
|
65
|
+
this.streamTicket = resp.stream_ticket;
|
|
66
|
+
logger.debug(`WeixinStreamSender.init: streamId=${this.clientStreamId}`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Send a single piece of data on the stream.
|
|
70
|
+
* piece_seq auto-increments starting from 1.
|
|
71
|
+
*
|
|
72
|
+
* If a previous call failed, the unsent pieces are automatically prepended
|
|
73
|
+
* to this request so they are retried with their original piece_seq values.
|
|
74
|
+
*/
|
|
75
|
+
async sendPiece(data) {
|
|
76
|
+
this.assertReady();
|
|
77
|
+
// Remember the seq checkpoint *before* we assign a new seq, so we can
|
|
78
|
+
// roll back on failure.
|
|
79
|
+
const seqBefore = this.pendingPieces.length > 0
|
|
80
|
+
? this.seqBeforePending
|
|
81
|
+
: this.pieceSeq;
|
|
82
|
+
this.pieceSeq += 1;
|
|
83
|
+
const newPiece = {
|
|
84
|
+
piece_seq: this.pieceSeq,
|
|
85
|
+
piece_data: Buffer.from(JSON.stringify(data), "utf-8").toString("base64"),
|
|
86
|
+
};
|
|
87
|
+
// Merge any previously-failed pieces with the new one.
|
|
88
|
+
const pieces = [...this.pendingPieces, newPiece];
|
|
89
|
+
try {
|
|
90
|
+
const resp = await syncStream({
|
|
91
|
+
baseUrl: this.opts.baseUrl,
|
|
92
|
+
token: this.opts.token,
|
|
93
|
+
timeoutMs: this.opts.timeoutMs,
|
|
94
|
+
accountId: this.opts.accountId,
|
|
95
|
+
body: {
|
|
96
|
+
device_id: this.deviceId,
|
|
97
|
+
client_stream_id: this.clientStreamId,
|
|
98
|
+
business_type: STREAM_BUSINESS_TYPE,
|
|
99
|
+
up_piece_list: pieces,
|
|
100
|
+
end_up_piece_seq: 0,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
this.checkAbort(resp);
|
|
104
|
+
// Success — clear pending state.
|
|
105
|
+
const retried = pieces.length - 1;
|
|
106
|
+
this.pendingPieces = [];
|
|
107
|
+
this.seqBeforePending = 0;
|
|
108
|
+
if (retried > 0) {
|
|
109
|
+
logger.info(`WeixinStreamSender.sendPiece: seq=${this.pieceSeq} type=${data.type} batch=${pieces.length} retried=${retried} streamId=${this.clientStreamId}`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
logger.debug(`WeixinStreamSender.sendPiece: seq=${this.pieceSeq} type=${data.type} batch=${pieces.length}`);
|
|
113
|
+
}
|
|
114
|
+
return resp;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
// Roll back: save all pieces for retry and restore pieceSeq.
|
|
118
|
+
this.pendingPieces = pieces;
|
|
119
|
+
this.seqBeforePending = seqBefore;
|
|
120
|
+
this.pieceSeq = seqBefore;
|
|
121
|
+
const seqs = pieces.map((p) => p.piece_seq).join(",");
|
|
122
|
+
logger.warn(`WeixinStreamSender.sendPiece: failed — kept pendingSeqs=[${seqs}] rolledBackTo=${seqBefore} type=${data.type} streamId=${this.clientStreamId} err=${String(err)}`);
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Signal that the uplink stream has ended.
|
|
128
|
+
* Optionally sends a final piece in the same request as the end marker.
|
|
129
|
+
* Any pending (previously-failed) pieces are included in the same request.
|
|
130
|
+
*/
|
|
131
|
+
async end(finalData) {
|
|
132
|
+
this.assertReady();
|
|
133
|
+
this.pieceSeq += 1;
|
|
134
|
+
const finalPiece = {
|
|
135
|
+
piece_seq: this.pieceSeq,
|
|
136
|
+
piece_data: Buffer.from(JSON.stringify(finalData ?? { type: "text", text: "" }), "utf-8").toString("base64"),
|
|
137
|
+
};
|
|
138
|
+
const pieces = [...this.pendingPieces, finalPiece];
|
|
139
|
+
const pendingCarried = this.pendingPieces.length;
|
|
140
|
+
this.ended = true;
|
|
141
|
+
let resp;
|
|
142
|
+
try {
|
|
143
|
+
resp = await syncStream({
|
|
144
|
+
baseUrl: this.opts.baseUrl,
|
|
145
|
+
token: this.opts.token,
|
|
146
|
+
timeoutMs: this.opts.timeoutMs,
|
|
147
|
+
accountId: this.opts.accountId,
|
|
148
|
+
body: {
|
|
149
|
+
device_id: this.deviceId,
|
|
150
|
+
client_stream_id: this.clientStreamId,
|
|
151
|
+
business_type: STREAM_BUSINESS_TYPE,
|
|
152
|
+
up_piece_list: pieces,
|
|
153
|
+
end_up_piece_seq: this.pieceSeq,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
const seqs = pieces.map((p) => p.piece_seq).join(",");
|
|
159
|
+
logger.warn(`WeixinStreamSender.end: failed — endSeq=${this.pieceSeq} batch=${pieces.length} carriedPending=${pendingCarried} seqs=[${seqs}] streamId=${this.clientStreamId} err=${String(err)}`);
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
this.pendingPieces = [];
|
|
163
|
+
this.seqBeforePending = 0;
|
|
164
|
+
if (pendingCarried > 0) {
|
|
165
|
+
logger.info(`WeixinStreamSender.end: endSeq=${this.pieceSeq} batch=${pieces.length} retried=${pendingCarried} streamId=${this.clientStreamId}`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
logger.debug(`WeixinStreamSender.end: endSeq=${this.pieceSeq} batch=${pieces.length}`);
|
|
169
|
+
}
|
|
170
|
+
return resp;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Send a client-side abort signal.
|
|
174
|
+
*/
|
|
175
|
+
async abort(errorMsg) {
|
|
176
|
+
this.assertReady();
|
|
177
|
+
this.ended = true;
|
|
178
|
+
let resp;
|
|
179
|
+
try {
|
|
180
|
+
resp = await syncStream({
|
|
181
|
+
baseUrl: this.opts.baseUrl,
|
|
182
|
+
token: this.opts.token,
|
|
183
|
+
timeoutMs: this.opts.timeoutMs,
|
|
184
|
+
accountId: this.opts.accountId,
|
|
185
|
+
body: {
|
|
186
|
+
device_id: this.deviceId,
|
|
187
|
+
client_stream_id: this.clientStreamId,
|
|
188
|
+
business_type: STREAM_BUSINESS_TYPE,
|
|
189
|
+
up_piece_list: [],
|
|
190
|
+
end_up_piece_seq: this.pieceSeq || 1,
|
|
191
|
+
abort_info: {
|
|
192
|
+
abort_type: AbortType.CLIENT_ABORT,
|
|
193
|
+
abort_detail_error_code: 0,
|
|
194
|
+
abort_detail_error_msg: errorMsg ?? "client abort",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
logger.warn(`WeixinStreamSender.abort: failed streamId=${this.clientStreamId} reason=${errorMsg ?? "client abort"} err=${String(err)}`);
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
logger.debug(`WeixinStreamSender.abort: streamId=${this.clientStreamId} reason=${errorMsg ?? "client abort"}`);
|
|
204
|
+
return resp;
|
|
205
|
+
}
|
|
206
|
+
get currentPieceSeq() {
|
|
207
|
+
return this.pieceSeq;
|
|
208
|
+
}
|
|
209
|
+
get streamId() {
|
|
210
|
+
return this.clientStreamId;
|
|
211
|
+
}
|
|
212
|
+
get ticket() {
|
|
213
|
+
return this.streamTicket;
|
|
214
|
+
}
|
|
215
|
+
get isEnded() {
|
|
216
|
+
return this.ended;
|
|
217
|
+
}
|
|
218
|
+
assertReady() {
|
|
219
|
+
if (!this.streamTicket) {
|
|
220
|
+
throw new Error("WeixinStreamSender: not initialized — call init() first");
|
|
221
|
+
}
|
|
222
|
+
if (this.ended) {
|
|
223
|
+
throw new Error("WeixinStreamSender: stream already ended");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
checkAbort(resp) {
|
|
227
|
+
if (resp.abort_info?.abort_type) {
|
|
228
|
+
const info = resp.abort_info;
|
|
229
|
+
logger.warn(`WeixinStreamSender: server abort type=${info.abort_type} code=${info.abort_detail_error_code} msg=${info.abort_detail_error_msg}`);
|
|
230
|
+
this.ended = true;
|
|
231
|
+
throw new Error(`Stream aborted: type=${info.abort_type} code=${info.abort_detail_error_code} msg=${info.abort_detail_error_msg ?? ""}`);
|
|
232
|
+
}
|
|
233
|
+
if (resp.base_response?.ret && resp.base_response.ret !== 0) {
|
|
234
|
+
logger.warn(`WeixinStreamSender: syncStream error ret=${resp.base_response.ret} errmsg=${resp.base_response.errmsg}`);
|
|
235
|
+
throw new Error(`syncStream failed: ret=${resp.base_response.ret} errmsg=${resp.base_response.errmsg ?? ""}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../../src/streaming/stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function parseFenceSpans(buffer) {
|
|
2
|
+
const spans = [];
|
|
3
|
+
let open;
|
|
4
|
+
let offset = 0;
|
|
5
|
+
while (offset <= buffer.length) {
|
|
6
|
+
const nextNewline = buffer.indexOf("\n", offset);
|
|
7
|
+
const lineEnd = nextNewline === -1 ? buffer.length : nextNewline;
|
|
8
|
+
const line = buffer.slice(offset, lineEnd);
|
|
9
|
+
const match = /^( {0,3})(`{3,}|~{3,})(.*)$/.exec(line);
|
|
10
|
+
if (match) {
|
|
11
|
+
const marker = match[2];
|
|
12
|
+
const markerChar = marker[0];
|
|
13
|
+
const markerLen = marker.length;
|
|
14
|
+
if (!open) {
|
|
15
|
+
open = { start: offset, markerChar, markerLen };
|
|
16
|
+
}
|
|
17
|
+
else if (open.markerChar === markerChar && markerLen >= open.markerLen) {
|
|
18
|
+
spans.push({ start: open.start, end: lineEnd });
|
|
19
|
+
open = undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (nextNewline === -1)
|
|
23
|
+
break;
|
|
24
|
+
offset = nextNewline + 1;
|
|
25
|
+
}
|
|
26
|
+
if (open) {
|
|
27
|
+
spans.push({ start: open.start, end: buffer.length });
|
|
28
|
+
}
|
|
29
|
+
return spans;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Return the fence span containing `index`, or undefined if `index` is not
|
|
33
|
+
* inside any fence. Spans are assumed to be sorted by `start` (which is the
|
|
34
|
+
* natural output of parseFenceSpans).
|
|
35
|
+
*/
|
|
36
|
+
export function findFenceSpanAt(spans, index) {
|
|
37
|
+
let low = 0;
|
|
38
|
+
let high = spans.length - 1;
|
|
39
|
+
while (low <= high) {
|
|
40
|
+
const mid = (low + high) >>> 1;
|
|
41
|
+
const span = spans[mid];
|
|
42
|
+
if (index < span.start) {
|
|
43
|
+
high = mid - 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (index >= span.end) {
|
|
47
|
+
low = mid + 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
return span;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=markdown-fences.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-fences.js","sourceRoot":"","sources":["../../../src/util/markdown-fences.ts"],"names":[],"mappings":"AAoBA,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,IAAI,IAA0E,CAAC;IAC/E,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,OAAO,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC;QACjE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;YAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;YAChC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,IAAI,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;YAClD,CAAC;iBAAM,IAAI,IAAI,CAAC,UAAU,KAAK,UAAU,IAAI,SAAS,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACzE,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;gBAChD,IAAI,GAAG,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QACD,IAAI,WAAW,KAAK,CAAC,CAAC;YAAE,MAAM;QAC9B,MAAM,GAAG,WAAW,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,IAAI,EAAE,CAAC;QACT,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAA2B,EAAE,KAAa;IACxE,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC5B,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAE,CAAC;QACzB,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACtB,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-sch
|
|
|
4
4
|
import { weixinPlugin } from "./src/channel.js";
|
|
5
5
|
import { assertHostCompatibility } from "./src/compat.js";
|
|
6
6
|
import { WeixinConfigSchema } from "./src/config/config-schema.js";
|
|
7
|
-
import { setWeixinRuntime } from "./src/runtime.js";
|
|
8
7
|
|
|
9
8
|
export default {
|
|
10
9
|
id: "openclaw-weixin",
|
|
@@ -15,10 +14,6 @@ export default {
|
|
|
15
14
|
// Fail-fast: reject incompatible host versions before any side-effects.
|
|
16
15
|
assertHostCompatibility(api.runtime?.version);
|
|
17
16
|
|
|
18
|
-
if (api.runtime) {
|
|
19
|
-
setWeixinRuntime(api.runtime);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
api.registerChannel({ plugin: weixinPlugin });
|
|
23
18
|
},
|
|
24
19
|
};
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/api/api.ts
CHANGED
|
@@ -201,11 +201,10 @@ function buildCommonHeaders(): Record<string, string> {
|
|
|
201
201
|
return headers;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
function buildHeaders(opts: { token?: string
|
|
204
|
+
function buildHeaders(opts: { token?: string }): Record<string, string> {
|
|
205
205
|
const headers: Record<string, string> = {
|
|
206
206
|
"Content-Type": "application/json",
|
|
207
207
|
AuthorizationType: "ilink_bot_token",
|
|
208
|
-
"Content-Length": String(Buffer.byteLength(opts.body, "utf-8")),
|
|
209
208
|
"X-WECHAT-UIN": randomWechatUin(),
|
|
210
209
|
...buildCommonHeaders(),
|
|
211
210
|
};
|
|
@@ -277,7 +276,7 @@ export async function apiPostFetch(params: {
|
|
|
277
276
|
}): Promise<string> {
|
|
278
277
|
const base = ensureTrailingSlash(params.baseUrl);
|
|
279
278
|
const url = new URL(params.endpoint, base);
|
|
280
|
-
const hdrs = buildHeaders({ token: params.token
|
|
279
|
+
const hdrs = buildHeaders({ token: params.token });
|
|
281
280
|
logger.debug(`POST ${redactUrl(url.toString())} body=${redactBody(params.body)}`);
|
|
282
281
|
|
|
283
282
|
const controller =
|
package/src/auth/accounts.ts
CHANGED
|
@@ -4,7 +4,6 @@ import path from "node:path";
|
|
|
4
4
|
import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
5
5
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
6
6
|
|
|
7
|
-
import { getWeixinRuntime } from "../runtime.js";
|
|
8
7
|
import { resolveStateDir } from "../storage/state-dir.js";
|
|
9
8
|
import { resolveFrameworkAllowFromPath } from "./pairing.js";
|
|
10
9
|
import { logger } from "../util/logger.js";
|