@xmanrui/dsh-im 4.22.0 → 4.24.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.
- package/README.en.md +5 -4
- package/README.md +5 -4
- package/lib/client.js +2584 -1153
- package/lib/index.js +290 -298
- package/package.json +14 -2
- package/plugin-src/client/channel-card-meta.js +2 -0
- package/plugin-src/client/channel-logos.js +11 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -1
- package/plugin-src/client/channels/dingtalk/index.js +15 -13
- package/plugin-src/client/channels/email/api.js +52 -0
- package/plugin-src/client/channels/email/index.js +728 -0
- package/plugin-src/client/channels/email/styles.js +51 -0
- package/plugin-src/client/channels/feishu/api.js +4 -2
- package/plugin-src/client/channels/feishu/index.js +23 -7
- package/plugin-src/client/channels/imessage/index.js +2 -1
- package/plugin-src/client/channels/office/api.js +2 -0
- package/plugin-src/client/channels/office/index.js +6 -5
- package/plugin-src/client/channels/qq/api.js +7 -0
- package/plugin-src/client/channels/qq/index.js +16 -3
- package/plugin-src/client/channels/shared/token-api.js +8 -1
- package/plugin-src/client/channels/shared/token-channel.js +28 -3
- package/plugin-src/client/channels/slack/index.js +2 -1
- package/plugin-src/client/channels/telegram/index.js +3 -0
- package/plugin-src/client/channels/telegram/styles.js +12 -0
- package/plugin-src/client/channels/telegram/thinking-traces.js +25 -0
- package/plugin-src/client/channels/wecom/api.js +7 -1
- package/plugin-src/client/channels/wecom/index.js +16 -3
- package/plugin-src/client/channels/wecom-app/api.js +6 -1
- package/plugin-src/client/channels/wecom-app/index.js +16 -3
- package/plugin-src/client/channels/weixin/api.js +2 -1
- package/plugin-src/client/channels/weixin/connection-error.js +1 -71
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +16 -3
- package/plugin-src/client/connection-error.js +87 -0
- package/plugin-src/client/global-settings.js +81 -1
- package/plugin-src/client/i18n.js +69 -1
- package/plugin-src/client/index.js +40 -4
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +8 -2
- package/plugin-src/host/build.mjs +4 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +2 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +7 -4
- package/plugin-src/host/channels/email/availability.mjs +60 -0
- package/plugin-src/host/channels/email/index.mjs +36 -0
- package/plugin-src/host/channels/email/production.mjs +58 -0
- package/plugin-src/host/channels/email/rpc.mjs +163 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +7 -4
- package/plugin-src/host/channels/imessage/rpc.mjs +13 -6
- package/plugin-src/host/channels/office/rpc.mjs +3 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/qq/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/production.mjs +52 -5
- package/plugin-src/host/channels/shared/rpc.mjs +14 -3
- package/plugin-src/host/channels/shared/startup-error.mjs +4 -3
- package/plugin-src/host/channels/shared/startup.mjs +11 -6
- package/plugin-src/host/channels/shared/thinking-traces-rpc.mjs +11 -0
- package/plugin-src/host/channels/slack/production.mjs +2 -0
- package/plugin-src/host/channels/slack/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom/production.mjs +2 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom-app/production.mjs +2 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +5 -3
- package/plugin-src/host/channels/weixin/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +5 -3
- package/plugin-src/host/delivery-adapter.mjs +11 -0
- package/plugin-src/host/image-input-rpc.mjs +24 -0
- package/plugin-src/host/inbound-ttl-rpc.mjs +6 -1
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/host/modern-harness-api.mjs +7 -2
- package/plugin-src/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +13 -5
- package/src/channels/dingtalk/connection-error.mjs +5 -12
- package/src/channels/dingtalk/device-auth.mjs +4 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -4
- package/src/channels/dingtalk/dingtalk-controller.mjs +60 -45
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -4
- package/src/channels/discord/discord-api.mjs +3 -3
- package/src/channels/discord/discord-runtime.mjs +18 -12
- package/src/channels/email/config-store.mjs +194 -0
- package/src/channels/email/email-api.mjs +20 -0
- package/src/channels/email/email-bridge.mjs +17 -0
- package/src/channels/email/email-controller.mjs +824 -0
- package/src/channels/email/email-runtime.mjs +699 -0
- package/src/channels/email/harness-client.mjs +7 -0
- package/src/channels/email/mail-format.mjs +91 -0
- package/src/channels/email/state-store.mjs +176 -0
- package/src/channels/email/transport.mjs +64 -0
- package/src/channels/email/transports/agent-mail.mjs +604 -0
- package/src/channels/email/transports/agently-cli.mjs +288 -0
- package/src/channels/email/transports/imap-smtp.mjs +165 -0
- package/src/channels/feishu/bridge.mjs +39 -11
- package/src/channels/feishu/feishu-channel.mjs +35 -0
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- package/src/channels/feishu/live-cot.mjs +260 -0
- package/src/channels/feishu/message-utils.mjs +3 -2
- package/src/channels/feishu/multi-bot-controller.mjs +56 -47
- package/src/channels/feishu/registration-manager.mjs +8 -4
- package/src/channels/feishu/repair-manager.mjs +2 -1
- package/src/channels/feishu/slash-command-registry.mjs +17 -0
- package/src/channels/feishu/step-push-mode.mjs +10 -4
- package/src/channels/imessage/runtime.mjs +9 -5
- package/src/channels/office/office-controller.mjs +23 -12
- package/src/channels/office/office-runtime.mjs +6 -3
- package/src/channels/office/office-transport.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +7 -5
- package/src/channels/qq/qq-controller.mjs +56 -42
- package/src/channels/qq/qq-runtime.mjs +16 -11
- package/src/channels/shared/bot-workspace-store.mjs +29 -10
- package/src/channels/shared/connection-error.mjs +191 -0
- package/src/channels/shared/connection-test.mjs +3 -1
- package/src/channels/shared/conversation-state-store.mjs +46 -1
- package/src/channels/shared/diagnostic-details.mjs +95 -0
- package/src/channels/shared/harness-client.mjs +287 -57
- package/src/channels/shared/i18n-en/diagnostics.mjs +55 -0
- package/src/channels/shared/i18n-en/email.mjs +25 -0
- package/src/channels/shared/i18n-en/image-input.mjs +17 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +1 -1
- package/src/channels/shared/i18n-en.mjs +6 -0
- package/src/channels/shared/image-input-policy.mjs +35 -0
- package/src/channels/shared/image-input-settings-store.mjs +60 -0
- package/src/channels/shared/image-input.mjs +124 -0
- package/src/channels/shared/image-prompt.mjs +15 -7
- package/src/channels/shared/message-failure.mjs +12 -0
- package/src/channels/shared/semantic/reply-reference.mjs +3 -3
- package/src/channels/shared/text-harness-bridge.mjs +150 -25
- package/src/channels/shared/token-bot-controller.mjs +52 -47
- package/src/channels/slack/slack-api.mjs +3 -3
- package/src/channels/slack/slack-controller.mjs +59 -51
- package/src/channels/slack/slack-runtime.mjs +18 -12
- package/src/channels/telegram/config-store.mjs +4 -1
- package/src/channels/telegram/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-controller.mjs +13 -1
- package/src/channels/telegram/telegram-runtime.mjs +215 -14
- package/src/channels/wecom/wecom-bridge.mjs +15 -6
- package/src/channels/wecom/wecom-controller.mjs +51 -37
- package/src/channels/wecom/wecom-runtime.mjs +10 -6
- package/src/channels/wecom-app/wecom-app-bridge.mjs +7 -5
- package/src/channels/wecom-app/wecom-app-controller.mjs +50 -37
- package/src/channels/wecom-app/wecom-app-runtime.mjs +2 -1
- package/src/channels/weixin/connection-error.mjs +26 -92
- package/src/channels/weixin/diagnostic-details.mjs +1 -63
- package/src/channels/weixin/weixin-bridge.mjs +7 -5
- package/src/channels/whatsapp/whatsapp-controller.mjs +34 -17
- package/src/channels/whatsapp/whatsapp-runtime.mjs +35 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createConnectionDiagnostics, atConnectionStage } from '../shared/connection-error.mjs';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { FeishuHarnessBridge } from './bridge.mjs';
|
|
3
4
|
import { cardActionProbeCard } from './feishu-cards.mjs';
|
|
@@ -125,6 +126,7 @@ export class FeishuRuntime {
|
|
|
125
126
|
#requestTimeoutMs;
|
|
126
127
|
#wsAgent;
|
|
127
128
|
#logger;
|
|
129
|
+
#diagnostics;
|
|
128
130
|
#repair;
|
|
129
131
|
#client = null;
|
|
130
132
|
#bridge = null;
|
|
@@ -200,7 +202,7 @@ export class FeishuRuntime {
|
|
|
200
202
|
this.#requestTimeoutMs = requestTimeoutMs;
|
|
201
203
|
this.#slashCommands = Boolean(slashCommands);
|
|
202
204
|
this.#wsAgent = wsAgent;
|
|
203
|
-
this.#logger = logger;
|
|
205
|
+
this.#logger = logger; this.#diagnostics = createConnectionDiagnostics({ channel: 'feishu', logger });
|
|
204
206
|
this.#status = createBridgeStatus({ allowedSenderCount: normalizedOwners.length });
|
|
205
207
|
}
|
|
206
208
|
|
|
@@ -277,10 +279,10 @@ export class FeishuRuntime {
|
|
|
277
279
|
};
|
|
278
280
|
this.#status.startedAt = new Date().toISOString();
|
|
279
281
|
this.#status.feishuLongConnectionState = 'connecting';
|
|
280
|
-
this.#status.lastError = null;
|
|
282
|
+
this.#status.lastError = null; this.#status.error = null; this.#diagnostics.clear();
|
|
281
283
|
|
|
282
284
|
try {
|
|
283
|
-
await this.#harness.ensureRunning({ signal });
|
|
285
|
+
await atConnectionStage('harness.check', () => this.#harness.ensureRunning({ signal }));
|
|
284
286
|
assertCurrentStart();
|
|
285
287
|
this.#status.harnessReachable = true;
|
|
286
288
|
|
|
@@ -391,15 +393,16 @@ export class FeishuRuntime {
|
|
|
391
393
|
if (!isCurrentStart()) return;
|
|
392
394
|
this.#status.feishuLongConnectionState = 'connected';
|
|
393
395
|
this.#status.ready = true;
|
|
394
|
-
this.#status.lastError = null;
|
|
396
|
+
this.#status.lastError = null; this.#status.error = null; this.#diagnostics.clear();
|
|
395
397
|
settleReady();
|
|
396
398
|
},
|
|
397
399
|
onError: (error) => {
|
|
398
400
|
if (!isCurrentStart()) return;
|
|
399
401
|
this.#status.feishuLongConnectionState = 'failed';
|
|
400
402
|
this.#status.ready = false;
|
|
401
|
-
this.#status.
|
|
402
|
-
this.#
|
|
403
|
+
this.#status.error = this.#diagnostics.report(error, { operation: 'connection.monitor', botId: this.#botId, automatic: true }).publicError;
|
|
404
|
+
this.#status.lastError = this.#status.error.message;
|
|
405
|
+
|
|
403
406
|
settleError(error);
|
|
404
407
|
},
|
|
405
408
|
onReconnecting: () => {
|
|
@@ -411,7 +414,7 @@ export class FeishuRuntime {
|
|
|
411
414
|
if (!isCurrentStart()) return;
|
|
412
415
|
this.#status.feishuLongConnectionState = 'connected';
|
|
413
416
|
this.#status.ready = true;
|
|
414
|
-
this.#status.lastError = null;
|
|
417
|
+
this.#status.lastError = null; this.#status.error = null; this.#diagnostics.clear();
|
|
415
418
|
},
|
|
416
419
|
});
|
|
417
420
|
this.#wsClient = wsClient;
|
|
@@ -436,7 +439,8 @@ export class FeishuRuntime {
|
|
|
436
439
|
if (signal.aborted) throw error;
|
|
437
440
|
this.#status.ready = false;
|
|
438
441
|
this.#status.feishuLongConnectionState = 'failed';
|
|
439
|
-
this.#status.
|
|
442
|
+
this.#status.error = this.#diagnostics.report(error, { operation: 'connection.restore', reuse: true, botId: this.#botId, automatic: true }).publicError;
|
|
443
|
+
this.#status.lastError = this.#status.error.message;
|
|
440
444
|
await this.#cleanup({ preserveError: true, abortController });
|
|
441
445
|
throw error;
|
|
442
446
|
}
|
|
@@ -720,6 +724,7 @@ export class FeishuRuntime {
|
|
|
720
724
|
|
|
721
725
|
async #cleanup({ preserveError = false, abortController } = {}) {
|
|
722
726
|
const error = preserveError ? this.#status.lastError : null;
|
|
727
|
+
const diagnostic = preserveError ? this.#status.error : null;
|
|
723
728
|
if (this.#abortController === abortController) this.#abortController = null;
|
|
724
729
|
abortController?.abort(new DOMException('Feishu runtime stopped', 'AbortError'));
|
|
725
730
|
for (const probe of this.#pendingCardActionProbes.values()) {
|
|
@@ -742,7 +747,9 @@ export class FeishuRuntime {
|
|
|
742
747
|
this.#client = null;
|
|
743
748
|
this.#status.feishuLongConnectionState = preserveError ? 'failed' : 'idle';
|
|
744
749
|
this.#status.slashCommandRegistration = 'idle';
|
|
750
|
+
this.#status.error = diagnostic;
|
|
745
751
|
this.#status.lastError = error;
|
|
752
|
+
if (!preserveError) this.#diagnostics.clear();
|
|
746
753
|
return this.status;
|
|
747
754
|
}
|
|
748
755
|
}
|
|
@@ -27,7 +27,7 @@ export class GroupMessagePermissionManager {
|
|
|
27
27
|
#appId;
|
|
28
28
|
#domain;
|
|
29
29
|
|
|
30
|
-
constructor({ registerApp, onCredentials, appId, domain = 'feishu' } = {}) {
|
|
30
|
+
constructor({ registerApp, onCredentials, appId, domain = 'feishu', diagnostics } = {}) {
|
|
31
31
|
if (typeof registerApp !== 'function') throw new TypeError('registerApp is required');
|
|
32
32
|
if (typeof onCredentials !== 'function') throw new TypeError('onCredentials is required');
|
|
33
33
|
if (typeof appId !== 'string' || !appId.trim()) throw new TypeError('appId is required');
|
|
@@ -36,6 +36,7 @@ export class GroupMessagePermissionManager {
|
|
|
36
36
|
this.#appId = appId.trim();
|
|
37
37
|
this.#domain = domain;
|
|
38
38
|
this.#manager = new RegistrationManager({
|
|
39
|
+
diagnostics,
|
|
39
40
|
registerApp: (options) => registerApp({
|
|
40
41
|
...options,
|
|
41
42
|
onQRCodeReady: (info) => {
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
const MAX_EVENTS_PER_WRITE = 50;
|
|
2
|
+
const MAX_EVENT_CONTENT_CHARS = 4096;
|
|
3
|
+
const MAX_TOOL_RESULT_CHARS = 1500;
|
|
4
|
+
|
|
5
|
+
const TOOL_ICONS = Object.freeze({
|
|
6
|
+
read: 'read',
|
|
7
|
+
edit: 'write',
|
|
8
|
+
delete: 'write',
|
|
9
|
+
move: 'write',
|
|
10
|
+
search: 'search',
|
|
11
|
+
fetch: 'search',
|
|
12
|
+
execute: 'bash',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
let lastTimestamp = 0;
|
|
16
|
+
|
|
17
|
+
function boundedEventContent(content) {
|
|
18
|
+
const encoded = JSON.stringify(content);
|
|
19
|
+
if (encoded.length <= MAX_EVENT_CONTENT_CHARS) return encoded;
|
|
20
|
+
const field = typeof content?.delta === 'string'
|
|
21
|
+
? 'delta'
|
|
22
|
+
: typeof content?.message === 'string' ? 'message' : null;
|
|
23
|
+
if (!field) return JSON.stringify({ ...content, truncated: true });
|
|
24
|
+
|
|
25
|
+
const truncated = { ...content, truncated: true, [field]: content[field] };
|
|
26
|
+
while (truncated[field].length > 0
|
|
27
|
+
&& JSON.stringify(truncated).length > MAX_EVENT_CONTENT_CHARS) {
|
|
28
|
+
truncated[field] = truncated[field].slice(0, Math.max(0, truncated[field].length - 256));
|
|
29
|
+
}
|
|
30
|
+
if (truncated[field].length === 0
|
|
31
|
+
&& JSON.stringify(truncated).length > MAX_EVENT_CONTENT_CHARS) {
|
|
32
|
+
return JSON.stringify({ truncated: true });
|
|
33
|
+
}
|
|
34
|
+
return JSON.stringify(truncated);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cotEvent(eventType, content) {
|
|
38
|
+
lastTimestamp = Math.max(Date.now(), lastTimestamp + 1);
|
|
39
|
+
return {
|
|
40
|
+
event_type: eventType,
|
|
41
|
+
content: boundedEventContent(content),
|
|
42
|
+
timestamp: String(lastTimestamp),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function boundedResult(value) {
|
|
47
|
+
const text = String(value ?? '');
|
|
48
|
+
return text.length <= MAX_TOOL_RESULT_CHARS
|
|
49
|
+
? text
|
|
50
|
+
: `${text.slice(0, MAX_TOOL_RESULT_CHARS - 1)}…`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toolKind(name) {
|
|
54
|
+
const value = String(name ?? '').toLowerCase();
|
|
55
|
+
if (/read|view|list|get/.test(value)) return 'read';
|
|
56
|
+
if (/edit|write|create|patch|update/.test(value)) return 'edit';
|
|
57
|
+
if (/delete|remove/.test(value)) return 'delete';
|
|
58
|
+
if (/move|rename/.test(value)) return 'move';
|
|
59
|
+
if (/search|grep|glob|find/.test(value)) return 'search';
|
|
60
|
+
if (/fetch|web|http|download/.test(value)) return 'fetch';
|
|
61
|
+
if (/shell|bash|command|exec|terminal/.test(value)) return 'execute';
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function turnFailure(reason) {
|
|
66
|
+
const kind = typeof reason === 'string' ? reason : reason?.kind;
|
|
67
|
+
if (reason === null || reason === undefined || kind === 'completed') return null;
|
|
68
|
+
if (kind !== 'error') {
|
|
69
|
+
return kind ? `Turn ended: ${kind}` : 'Turn failed';
|
|
70
|
+
}
|
|
71
|
+
const error = reason.error ?? reason.failure;
|
|
72
|
+
if (!error) return 'Turn failed';
|
|
73
|
+
return [error.code, error.message].filter(Boolean).join(': ') || 'Turn failed';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Maps Harness progress updates to Feishu's native thinking-process events.
|
|
78
|
+
* Process failures are contained here so they can never suppress the answer.
|
|
79
|
+
*/
|
|
80
|
+
export class FeishuLiveCot {
|
|
81
|
+
#channel;
|
|
82
|
+
#chatId;
|
|
83
|
+
#replyTo;
|
|
84
|
+
#hidden;
|
|
85
|
+
#onFailure;
|
|
86
|
+
#turn = null;
|
|
87
|
+
#opening = null;
|
|
88
|
+
#chain = Promise.resolve();
|
|
89
|
+
#pending = [];
|
|
90
|
+
#reasoningOpen = false;
|
|
91
|
+
#heldAssistant = null;
|
|
92
|
+
#finished = false;
|
|
93
|
+
#broken = false;
|
|
94
|
+
#messageIndex = 0;
|
|
95
|
+
|
|
96
|
+
constructor(channel, chatId, {
|
|
97
|
+
replyTo,
|
|
98
|
+
hidden = false,
|
|
99
|
+
onFailure = () => {},
|
|
100
|
+
} = {}) {
|
|
101
|
+
this.#channel = channel;
|
|
102
|
+
this.#chatId = chatId;
|
|
103
|
+
this.#replyTo = replyTo;
|
|
104
|
+
this.#hidden = hidden === true;
|
|
105
|
+
this.#onFailure = onFailure;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async #start(turn) {
|
|
109
|
+
if (this.#turn !== null || this.#broken) return;
|
|
110
|
+
this.#turn = Number.isSafeInteger(turn) ? turn : 0;
|
|
111
|
+
this.#opening = this.#channel.createCot(this.#chatId, {
|
|
112
|
+
...(this.#replyTo ? { replyTo: this.#replyTo } : {}),
|
|
113
|
+
hidden: this.#hidden,
|
|
114
|
+
}).catch((error) => {
|
|
115
|
+
this.#broken = true;
|
|
116
|
+
this.#onFailure(error);
|
|
117
|
+
return null;
|
|
118
|
+
});
|
|
119
|
+
await this.#write([
|
|
120
|
+
cotEvent('RUN_STARTED', {
|
|
121
|
+
threadId: this.#chatId,
|
|
122
|
+
runId: `turn-${this.#turn}`,
|
|
123
|
+
}),
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#write(events) {
|
|
128
|
+
if (this.#broken || events.length === 0) return;
|
|
129
|
+
this.#pending.push(...events);
|
|
130
|
+
this.#chain = this.#chain.then(async () => {
|
|
131
|
+
const handle = await this.#opening;
|
|
132
|
+
if (!handle || this.#broken) {
|
|
133
|
+
this.#pending.length = 0;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
while (this.#pending.length > 0) {
|
|
137
|
+
await this.#channel.writeCotEvents(
|
|
138
|
+
handle,
|
|
139
|
+
this.#pending.splice(0, MAX_EVENTS_PER_WRITE),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}).catch((error) => {
|
|
143
|
+
this.#broken = true;
|
|
144
|
+
this.#pending.length = 0;
|
|
145
|
+
this.#onFailure(error);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async #closeReasoning() {
|
|
150
|
+
if (!this.#reasoningOpen) return;
|
|
151
|
+
this.#reasoningOpen = false;
|
|
152
|
+
await this.#write([
|
|
153
|
+
cotEvent('REASONING_MESSAGE_END', {
|
|
154
|
+
messageId: `reasoning-${this.#turn}`,
|
|
155
|
+
}),
|
|
156
|
+
]);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async #flushHeldAssistant() {
|
|
160
|
+
const held = this.#heldAssistant;
|
|
161
|
+
this.#heldAssistant = null;
|
|
162
|
+
if (!held?.text) return;
|
|
163
|
+
const messageId = `text-${this.#turn}-${this.#messageIndex++}`;
|
|
164
|
+
await this.#write([
|
|
165
|
+
cotEvent('TEXT_MESSAGE_START', { messageId, role: 'assistant' }),
|
|
166
|
+
cotEvent('TEXT_MESSAGE_CONTENT', { messageId, delta: held.text }),
|
|
167
|
+
cotEvent('TEXT_MESSAGE_END', { messageId }),
|
|
168
|
+
]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async handle(update) {
|
|
172
|
+
if (!update || this.#finished || this.#broken) return;
|
|
173
|
+
if (update.type === 'turn-start') {
|
|
174
|
+
await this.#start(update.turn);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
await this.#start(update.turn);
|
|
178
|
+
if (update.type === 'reasoning') {
|
|
179
|
+
if (!update.text) return;
|
|
180
|
+
const messageId = `reasoning-${this.#turn}`;
|
|
181
|
+
if (!this.#reasoningOpen) {
|
|
182
|
+
this.#reasoningOpen = true;
|
|
183
|
+
await this.#write([
|
|
184
|
+
cotEvent('REASONING_MESSAGE_START', { messageId, role: 'reasoning' }),
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
await this.#write([
|
|
188
|
+
cotEvent('REASONING_MESSAGE_CONTENT', { messageId, delta: update.text }),
|
|
189
|
+
]);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (update.type === 'assistant-message') {
|
|
193
|
+
if (this.#heldAssistant) await this.#flushHeldAssistant();
|
|
194
|
+
this.#heldAssistant = update;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (update.type === 'tool') {
|
|
198
|
+
await this.#flushHeldAssistant();
|
|
199
|
+
await this.#closeReasoning();
|
|
200
|
+
const toolCallId = update.callId ?? `tool-${this.#turn}-${this.#messageIndex++}`;
|
|
201
|
+
const kind = toolKind(update.name);
|
|
202
|
+
await this.#write([
|
|
203
|
+
cotEvent('TOOL_CALL_START', {
|
|
204
|
+
toolCallId,
|
|
205
|
+
icon: TOOL_ICONS[kind] ?? 'default',
|
|
206
|
+
title: update.name || 'Tool',
|
|
207
|
+
toolCallName: update.name || 'tool',
|
|
208
|
+
}),
|
|
209
|
+
cotEvent('TOOL_CALL_ARGS', {
|
|
210
|
+
toolCallId,
|
|
211
|
+
delta: update.arguments ?? '',
|
|
212
|
+
}),
|
|
213
|
+
cotEvent('TOOL_CALL_END', { toolCallId }),
|
|
214
|
+
]);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (update.type === 'tool-result' && update.callId) {
|
|
218
|
+
await this.#write([
|
|
219
|
+
cotEvent('TOOL_CALL_RESULT', {
|
|
220
|
+
messageId: `result-${update.callId}`,
|
|
221
|
+
toolCallId: update.callId,
|
|
222
|
+
role: 'tool',
|
|
223
|
+
content: { type: 'code', code: boundedResult(update.text) },
|
|
224
|
+
...(update.errorCode ? { error: update.errorCode } : {}),
|
|
225
|
+
}),
|
|
226
|
+
]);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (update.type === 'turn-end') {
|
|
230
|
+
await this.finish(update.reason);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async finish(reason) {
|
|
235
|
+
if (this.#finished) {
|
|
236
|
+
await this.#chain;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.#finished = true;
|
|
240
|
+
this.#heldAssistant = null;
|
|
241
|
+
if (this.#turn === null || this.#broken) {
|
|
242
|
+
await this.#chain;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
await this.#closeReasoning();
|
|
246
|
+
const failure = reason instanceof Error
|
|
247
|
+
? reason.message
|
|
248
|
+
: turnFailure(reason);
|
|
249
|
+
await this.#write([
|
|
250
|
+
failure
|
|
251
|
+
? cotEvent('RUN_ERROR', { message: failure, code: 'TURN_FAILED' })
|
|
252
|
+
: cotEvent('RUN_FINISHED', {
|
|
253
|
+
threadId: this.#chatId,
|
|
254
|
+
runId: `turn-${this.#turn}`,
|
|
255
|
+
status: 'done',
|
|
256
|
+
}),
|
|
257
|
+
]);
|
|
258
|
+
await this.#chain;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { imageDownloadLimitMessage } from '../shared/image-prompt.mjs';
|
|
1
2
|
import { ImagePromptError } from '../shared/image-prompt.mjs';
|
|
2
3
|
import { t } from '../shared/i18n.mjs';
|
|
3
4
|
|
|
@@ -256,7 +257,7 @@ async function readBoundedStream(stream, { signal, maxBytes }) {
|
|
|
256
257
|
throw new ImagePromptError(
|
|
257
258
|
'image-too-large',
|
|
258
259
|
`Feishu image exceeds ${maxBytes} bytes`,
|
|
259
|
-
|
|
260
|
+
imageDownloadLimitMessage(maxBytes),
|
|
260
261
|
);
|
|
261
262
|
}
|
|
262
263
|
chunks.push(data);
|
|
@@ -392,7 +393,7 @@ function feishuImageSource(event, client, key) {
|
|
|
392
393
|
throw new ImagePromptError(
|
|
393
394
|
'image-too-large',
|
|
394
395
|
`Feishu image declares ${size} bytes; the limit is ${maxBytes}`,
|
|
395
|
-
|
|
396
|
+
imageDownloadLimitMessage(maxBytes),
|
|
396
397
|
);
|
|
397
398
|
}
|
|
398
399
|
return readBoundedStream(resource?.getReadableStream?.(), { signal, maxBytes });
|