@wu529778790/open-im 1.9.3 → 1.9.4-beta.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/dist/queue/request-queue.d.ts +2 -0
- package/dist/queue/request-queue.js +12 -0
- package/dist/workbuddy/centrifuge-client.d.ts +11 -0
- package/dist/workbuddy/centrifuge-client.js +114 -59
- package/dist/workbuddy/event-handler.js +30 -4
- package/dist/workbuddy/message-sender.d.ts +5 -0
- package/dist/workbuddy/message-sender.js +17 -0
- package/dist/workbuddy/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -2,5 +2,7 @@ export type EnqueueResult = 'running' | 'queued' | 'rejected';
|
|
|
2
2
|
export declare class RequestQueue {
|
|
3
3
|
private queues;
|
|
4
4
|
enqueue(userId: string, convId: string, prompt: string, execute: (prompt: string) => Promise<void>): EnqueueResult;
|
|
5
|
+
/** 清除指定用户会话的所有排队任务(不中止正在运行的任务) */
|
|
6
|
+
clear(userId: string, convId: string): number;
|
|
5
7
|
private run;
|
|
6
8
|
}
|
|
@@ -21,6 +21,18 @@ export class RequestQueue {
|
|
|
21
21
|
this.run(key, prompt, execute);
|
|
22
22
|
return 'running';
|
|
23
23
|
}
|
|
24
|
+
/** 清除指定用户会话的所有排队任务(不中止正在运行的任务) */
|
|
25
|
+
clear(userId, convId) {
|
|
26
|
+
const key = `${userId}:${convId}`;
|
|
27
|
+
const q = this.queues.get(key);
|
|
28
|
+
if (!q)
|
|
29
|
+
return 0;
|
|
30
|
+
const cleared = q.tasks.length;
|
|
31
|
+
q.tasks.length = 0;
|
|
32
|
+
if (cleared > 0)
|
|
33
|
+
log.info(`Cleared ${cleared} queued tasks for ${key}`);
|
|
34
|
+
return cleared;
|
|
35
|
+
}
|
|
24
36
|
async run(key, prompt, execute) {
|
|
25
37
|
try {
|
|
26
38
|
await execute(prompt);
|
|
@@ -42,9 +42,18 @@ export declare class WorkBuddyCentrifugeClient {
|
|
|
42
42
|
private state;
|
|
43
43
|
private processedMsgIds;
|
|
44
44
|
private static readonly MAX_MSG_ID_CACHE;
|
|
45
|
+
private streamingActive;
|
|
46
|
+
private subscribedChannels;
|
|
47
|
+
private lastErrorMsg;
|
|
48
|
+
private lastErrorTime;
|
|
49
|
+
private pendingResponses;
|
|
45
50
|
constructor(config: CentrifugeClientConfig, callbacks?: CentrifugeCallbacks);
|
|
46
51
|
get logPrefix(): string;
|
|
47
52
|
getState(): WorkBuddyState;
|
|
53
|
+
/** Enable/disable streaming mode: skip per-message channel registration during streaming. */
|
|
54
|
+
setStreamingMode(active: boolean): void;
|
|
55
|
+
/** Flush pending responses that failed to send. */
|
|
56
|
+
flushPendingResponses(): Promise<void>;
|
|
48
57
|
start(): void;
|
|
49
58
|
stop(): void;
|
|
50
59
|
setCallbacks(callbacks: Partial<CentrifugeCallbacks>): void;
|
|
@@ -71,6 +80,8 @@ export declare class WorkBuddyCentrifugeClient {
|
|
|
71
80
|
* Send prompt response (for WeChat KF, use HTTP instead)
|
|
72
81
|
*/
|
|
73
82
|
sendPromptResponse(payload: PromptResponsePayload, _guid?: string, _userId?: string): Promise<void>;
|
|
83
|
+
/** Send HTTP COPILOT_RESPONSE with retry and pending queue fallback. */
|
|
84
|
+
private sendHttpResponse;
|
|
74
85
|
/**
|
|
75
86
|
* Handle incoming publication from Centrifuge
|
|
76
87
|
*/
|
|
@@ -15,6 +15,11 @@ export class WorkBuddyCentrifugeClient {
|
|
|
15
15
|
state = 'disconnected';
|
|
16
16
|
processedMsgIds = new Set();
|
|
17
17
|
static MAX_MSG_ID_CACHE = 1000;
|
|
18
|
+
streamingActive = false;
|
|
19
|
+
subscribedChannels = new Set();
|
|
20
|
+
lastErrorMsg = '';
|
|
21
|
+
lastErrorTime = 0;
|
|
22
|
+
pendingResponses = [];
|
|
18
23
|
constructor(config, callbacks = {}) {
|
|
19
24
|
this.config = config;
|
|
20
25
|
this.callbacks = callbacks;
|
|
@@ -25,6 +30,29 @@ export class WorkBuddyCentrifugeClient {
|
|
|
25
30
|
getState() {
|
|
26
31
|
return this.state;
|
|
27
32
|
}
|
|
33
|
+
/** Enable/disable streaming mode: skip per-message channel registration during streaming. */
|
|
34
|
+
setStreamingMode(active) {
|
|
35
|
+
this.streamingActive = active;
|
|
36
|
+
if (!active) {
|
|
37
|
+
this.config.releaseChannelLockFn?.();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Flush pending responses that failed to send. */
|
|
41
|
+
async flushPendingResponses() {
|
|
42
|
+
if (this.pendingResponses.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
const pending = [...this.pendingResponses];
|
|
45
|
+
this.pendingResponses = [];
|
|
46
|
+
log.info(`${this.logPrefix} Flushing ${pending.length} pending responses`);
|
|
47
|
+
for (const item of pending) {
|
|
48
|
+
try {
|
|
49
|
+
await this.sendHttpResponse(item.payload);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
log.warn(`${this.logPrefix} Failed to flush pending response`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
28
56
|
start() {
|
|
29
57
|
if (this.state === 'connected' || this.state === 'connecting') {
|
|
30
58
|
log.info(`${this.logPrefix} Already connected or connecting`);
|
|
@@ -55,8 +83,16 @@ export class WorkBuddyCentrifugeClient {
|
|
|
55
83
|
}
|
|
56
84
|
});
|
|
57
85
|
this.client.on('error', (ctx) => {
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
const msg = ctx.error?.message || String(ctx.error);
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
// Suppress duplicate errors within 5 seconds to avoid log flooding
|
|
89
|
+
if (msg === this.lastErrorMsg && now - this.lastErrorTime < 5000) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.lastErrorMsg = msg;
|
|
93
|
+
this.lastErrorTime = now;
|
|
94
|
+
log.error(`${this.logPrefix} Error: ${msg}`);
|
|
95
|
+
this.callbacks.onError?.(new Error(msg));
|
|
60
96
|
});
|
|
61
97
|
// Create channel subscription
|
|
62
98
|
this.sub = this.client.newSubscription(this.config.channel, {
|
|
@@ -75,6 +111,10 @@ export class WorkBuddyCentrifugeClient {
|
|
|
75
111
|
log.info(`${this.logPrefix} Stopping...`);
|
|
76
112
|
this.state = 'disconnected';
|
|
77
113
|
this.processedMsgIds.clear();
|
|
114
|
+
this.streamingActive = false;
|
|
115
|
+
this.subscribedChannels.clear();
|
|
116
|
+
this.lastErrorMsg = '';
|
|
117
|
+
this.pendingResponses = [];
|
|
78
118
|
for (const sub of this.extraSubs) {
|
|
79
119
|
sub.unsubscribe();
|
|
80
120
|
}
|
|
@@ -100,6 +140,11 @@ export class WorkBuddyCentrifugeClient {
|
|
|
100
140
|
log.warn(`${this.logPrefix} Cannot subscribe: client not initialized`);
|
|
101
141
|
return;
|
|
102
142
|
}
|
|
143
|
+
// Skip if already subscribed to this channel
|
|
144
|
+
if (this.subscribedChannels.has(channel)) {
|
|
145
|
+
log.info(`${this.logPrefix} Already subscribed to: ${channel}, skipping duplicate`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
103
148
|
log.info(`${this.logPrefix} Subscribing to additional channel: ${channel}`);
|
|
104
149
|
const sub = this.client.newSubscription(channel, { token: subscriptionToken });
|
|
105
150
|
sub.on('publication', (ctx) => {
|
|
@@ -112,6 +157,7 @@ export class WorkBuddyCentrifugeClient {
|
|
|
112
157
|
log.info(`${this.logPrefix} Extra channel subscribed: ${channel}`);
|
|
113
158
|
});
|
|
114
159
|
this.extraSubs.push(sub);
|
|
160
|
+
this.subscribedChannels.add(channel);
|
|
115
161
|
sub.subscribe();
|
|
116
162
|
}
|
|
117
163
|
/**
|
|
@@ -144,14 +190,10 @@ export class WorkBuddyCentrifugeClient {
|
|
|
144
190
|
async sendPromptResponse(payload, _guid, _userId) {
|
|
145
191
|
// WeChat KF messages: send via HTTP COPILOT_RESPONSE
|
|
146
192
|
if (this.config.httpBaseUrl && this.config.httpAccessToken) {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
// The WorkBuddy server uses the registered channelId as the WeChat KF send_msg
|
|
150
|
-
// `touser`. Re-register the channel with the current WeChat user's externalUserId
|
|
151
|
-
// so that the server sends the reply to the correct customer.
|
|
193
|
+
const sessionId = payload.session_id;
|
|
194
|
+
// Register channel before sending (skip during streaming to reduce overhead)
|
|
152
195
|
const externalUserId = sessionId.includes('::') ? sessionId.split('::')[0] : null;
|
|
153
|
-
if (this.config.registerChannelFn && externalUserId) {
|
|
154
|
-
// Retry registerChannelFn up to 3 times on network failure
|
|
196
|
+
if (this.config.registerChannelFn && externalUserId && !this.streamingActive) {
|
|
155
197
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
156
198
|
try {
|
|
157
199
|
await this.config.registerChannelFn(externalUserId);
|
|
@@ -168,61 +210,74 @@ export class WorkBuddyCentrifugeClient {
|
|
|
168
210
|
}
|
|
169
211
|
}
|
|
170
212
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
213
|
+
await this.sendHttpResponse(payload);
|
|
214
|
+
// Release heartbeat lock (skip during streaming — released when streaming ends)
|
|
215
|
+
if (!this.streamingActive) {
|
|
216
|
+
this.config.releaseChannelLockFn?.();
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.sendEnvelope('session.promptResponse', payload, _guid, _userId);
|
|
221
|
+
}
|
|
222
|
+
/** Send HTTP COPILOT_RESPONSE with retry and pending queue fallback. */
|
|
223
|
+
async sendHttpResponse(payload) {
|
|
224
|
+
const message = payload.content?.map((c) => c.text).join('') || payload.error || '';
|
|
225
|
+
const sessionId = payload.session_id;
|
|
226
|
+
const isStreaming = payload.stop_reason === 'streaming';
|
|
227
|
+
const logLevel = isStreaming ? 'debug' : 'info';
|
|
228
|
+
const httpPayload = {
|
|
229
|
+
type: 'COPILOT_RESPONSE',
|
|
230
|
+
msgId: payload.prompt_id,
|
|
231
|
+
chatId: sessionId,
|
|
232
|
+
success: payload.stop_reason === 'end_turn' || isStreaming,
|
|
233
|
+
message,
|
|
234
|
+
metadata: {
|
|
235
|
+
sessionId: this.config.workspaceSessionId || sessionId,
|
|
236
|
+
requestId: payload.prompt_id,
|
|
237
|
+
state: payload.stop_reason === 'end_turn' ? 'completed' : payload.stop_reason,
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
const url = `${this.config.httpBaseUrl}/v2/backgroundagent/wecom/local-proxy/receive`;
|
|
241
|
+
log.debug(`${this.logPrefix} HTTP COPILOT_RESPONSE → chatId=${sessionId} msgLen=${message.length} streaming=${isStreaming}`);
|
|
242
|
+
const maxAttempts = isStreaming ? 2 : 5;
|
|
243
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
244
|
+
try {
|
|
245
|
+
const res = await fetch(url, {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: {
|
|
248
|
+
'Content-Type': 'application/json',
|
|
249
|
+
Authorization: `Bearer ${this.config.httpAccessToken}`,
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify(httpPayload),
|
|
252
|
+
signal: AbortSignal.timeout(30_000),
|
|
253
|
+
});
|
|
254
|
+
const body = await res.text().catch(() => '');
|
|
255
|
+
if (!res.ok) {
|
|
256
|
+
log.error(`${this.logPrefix} HTTP COPILOT_RESPONSE failed: ${res.status} ${body.substring(0, 300)}`);
|
|
207
257
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
log.warn(`${this.logPrefix} HTTP COPILOT_RESPONSE attempt ${attempt} failed, retrying in 2s:`, err);
|
|
211
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
log.error(`${this.logPrefix} HTTP COPILOT_RESPONSE error after 3 attempts:`, err);
|
|
215
|
-
}
|
|
258
|
+
else if (!isStreaming) {
|
|
259
|
+
log.info(`${this.logPrefix} HTTP COPILOT_RESPONSE ok: ${res.status} ${body.substring(0, 200)}`);
|
|
216
260
|
}
|
|
261
|
+
return; // sent successfully
|
|
217
262
|
}
|
|
218
|
-
|
|
219
|
-
|
|
263
|
+
catch (err) {
|
|
264
|
+
if (attempt < maxAttempts) {
|
|
265
|
+
const delay = 2000 * attempt;
|
|
266
|
+
log.warn(`${this.logPrefix} HTTP COPILOT_RESPONSE attempt ${attempt} failed, retrying in ${delay}ms:`, err);
|
|
267
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
log.error(`${this.logPrefix} HTTP COPILOT_RESPONSE error after ${maxAttempts} attempts:`, err);
|
|
271
|
+
}
|
|
220
272
|
}
|
|
221
|
-
// Release the heartbeat lock so the periodic registration can resume
|
|
222
|
-
this.config.releaseChannelLockFn?.();
|
|
223
|
-
return;
|
|
224
273
|
}
|
|
225
|
-
|
|
274
|
+
// All retries failed — queue for later (only non-streaming responses)
|
|
275
|
+
if (!isStreaming) {
|
|
276
|
+
this.pendingResponses.push({ payload, timestamp: Date.now() });
|
|
277
|
+
if (this.pendingResponses.length > 10)
|
|
278
|
+
this.pendingResponses.shift();
|
|
279
|
+
log.warn(`${this.logPrefix} Queued response for retry (queue: ${this.pendingResponses.length})`);
|
|
280
|
+
}
|
|
226
281
|
}
|
|
227
282
|
/**
|
|
228
283
|
* Handle incoming publication from Centrifuge
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { resolvePlatformAiCommand } from '../config.js';
|
|
5
5
|
import { AccessControl } from '../access/access-control.js';
|
|
6
6
|
import { RequestQueue } from '../queue/request-queue.js';
|
|
7
|
-
import { sendTextReply, sendErrorReply } from './message-sender.js';
|
|
7
|
+
import { sendTextReply, sendErrorReply, sendStreamingReply } from './message-sender.js';
|
|
8
8
|
import { CommandHandler } from '../commands/handler.js';
|
|
9
9
|
import { getAdapter } from '../adapters/registry.js';
|
|
10
10
|
import { runAITask } from '../shared/ai-task.js';
|
|
@@ -12,6 +12,7 @@ import { WORKBUDDY_THROTTLE_MS } from '../constants.js';
|
|
|
12
12
|
import { setActiveChatId } from '../shared/active-chats.js';
|
|
13
13
|
import { setChatUser } from '../shared/chat-user-map.js';
|
|
14
14
|
import { createLogger } from '../logger.js';
|
|
15
|
+
import { getCentrifugeClient } from './client.js';
|
|
15
16
|
const log = createLogger('WorkBuddyHandler');
|
|
16
17
|
export function setupWorkBuddyHandlers(config, sessionManager) {
|
|
17
18
|
const accessControl = new AccessControl(config.workbuddyAllowedUserIds);
|
|
@@ -42,14 +43,20 @@ export function setupWorkBuddyHandlers(config, sessionManager) {
|
|
|
42
43
|
const taskKey = `${userId}:${msgId}`;
|
|
43
44
|
await runAITask({ config, sessionManager }, { userId, chatId, workDir, sessionId, convId, platform: 'workbuddy', taskKey }, prompt, toolAdapter, {
|
|
44
45
|
throttleMs: WORKBUDDY_THROTTLE_MS,
|
|
46
|
+
minContentDeltaChars: 200,
|
|
45
47
|
streamUpdate: async (content) => {
|
|
46
|
-
|
|
47
|
-
log.debug(`Stream update (not sent): ${content.substring(0, 50)}...`);
|
|
48
|
+
await sendStreamingReply(null, chatId, content, msgId);
|
|
48
49
|
},
|
|
49
50
|
sendComplete: async (content) => {
|
|
51
|
+
const client = getCentrifugeClient();
|
|
52
|
+
if (client)
|
|
53
|
+
client.setStreamingMode(false);
|
|
50
54
|
await sendTextReply(null, chatId, content, msgId);
|
|
51
55
|
},
|
|
52
56
|
sendError: async (error) => {
|
|
57
|
+
const client = getCentrifugeClient();
|
|
58
|
+
if (client)
|
|
59
|
+
client.setStreamingMode(false);
|
|
53
60
|
await sendErrorReply(null, chatId, error, msgId);
|
|
54
61
|
},
|
|
55
62
|
extraCleanup: () => {
|
|
@@ -62,6 +69,12 @@ export function setupWorkBuddyHandlers(config, sessionManager) {
|
|
|
62
69
|
runningTasks.set(taskKey, state);
|
|
63
70
|
taskKeyByChatId.set(chatId, taskKey);
|
|
64
71
|
},
|
|
72
|
+
onFirstContent: () => {
|
|
73
|
+
// Enable streaming mode: register channel once, then skip per-update registration
|
|
74
|
+
const client = getCentrifugeClient();
|
|
75
|
+
if (client)
|
|
76
|
+
client.setStreamingMode(true);
|
|
77
|
+
},
|
|
65
78
|
});
|
|
66
79
|
}
|
|
67
80
|
async function handleEvent(chatId, msgId, content) {
|
|
@@ -91,6 +104,18 @@ export function setupWorkBuddyHandlers(config, sessionManager) {
|
|
|
91
104
|
try {
|
|
92
105
|
const handled = await commandHandler.dispatch(text, chatId, userId, 'workbuddy', (u, c, p, w, conv, _r, m) => handleAIRequest(u, c, msgId, p, w, conv));
|
|
93
106
|
if (handled) {
|
|
107
|
+
// /new 命令时,中止当前运行中的任务并清空队列
|
|
108
|
+
if (text === '/new') {
|
|
109
|
+
const runningTaskKey = taskKeyByChatId.get(chatId);
|
|
110
|
+
if (runningTaskKey) {
|
|
111
|
+
const state = runningTasks.get(runningTaskKey);
|
|
112
|
+
if (state) {
|
|
113
|
+
log.info(`Aborting running task ${runningTaskKey} due to /new command`);
|
|
114
|
+
state.handle.abort();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
requestQueue.clear(userId, convId);
|
|
118
|
+
}
|
|
94
119
|
log.info(`Command handled for message: ${text}`);
|
|
95
120
|
return;
|
|
96
121
|
}
|
|
@@ -110,7 +135,8 @@ export function setupWorkBuddyHandlers(config, sessionManager) {
|
|
|
110
135
|
await sendErrorReply(null, chatId, 'Request queue is full. Please try again later.', msgId);
|
|
111
136
|
}
|
|
112
137
|
else if (enqueueResult === 'queued') {
|
|
113
|
-
|
|
138
|
+
// 用 streaming 而非 end_turn,避免 CodeBuddy 平台显示 "✅ Local Agent task completed"
|
|
139
|
+
await sendStreamingReply(null, chatId, '⏳ 请求已排队,请稍候...', msgId);
|
|
114
140
|
}
|
|
115
141
|
}
|
|
116
142
|
return {
|
|
@@ -14,3 +14,8 @@ export declare function sendErrorReply(_client: WorkBuddyCentrifugeClient | null
|
|
|
14
14
|
* Send streaming chunk to WeChat KF
|
|
15
15
|
*/
|
|
16
16
|
export declare function sendStreamingChunk(_client: WorkBuddyCentrifugeClient | null, chatId: string, text: string, msgId: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Send streaming reply to WeChat KF via HTTP COPILOT_RESPONSE.
|
|
19
|
+
* Used for intermediate progress updates during AI task execution.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sendStreamingReply(_client: WorkBuddyCentrifugeClient | null, chatId: string, text: string, msgId: string): Promise<void>;
|
|
@@ -49,3 +49,20 @@ export function sendStreamingChunk(_client, chatId, text, msgId) {
|
|
|
49
49
|
}
|
|
50
50
|
client.sendMessageChunk(chatId, msgId, { type: 'text', text });
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Send streaming reply to WeChat KF via HTTP COPILOT_RESPONSE.
|
|
54
|
+
* Used for intermediate progress updates during AI task execution.
|
|
55
|
+
*/
|
|
56
|
+
export async function sendStreamingReply(_client, chatId, text, msgId) {
|
|
57
|
+
const client = _client ?? getCentrifugeClient();
|
|
58
|
+
if (!client) {
|
|
59
|
+
log.debug('WorkBuddy client not available, skipping streaming reply');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
await client.sendPromptResponse({
|
|
63
|
+
session_id: chatId,
|
|
64
|
+
prompt_id: msgId,
|
|
65
|
+
content: [{ type: 'text', text }],
|
|
66
|
+
stop_reason: 'streaming',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -82,5 +82,5 @@ export interface PromptResponsePayload {
|
|
|
82
82
|
text?: string;
|
|
83
83
|
}>;
|
|
84
84
|
error?: string;
|
|
85
|
-
stop_reason: 'end_turn' | 'max_tokens' | 'tool_use' | 'stop_sequence' | 'error';
|
|
85
|
+
stop_reason: 'end_turn' | 'max_tokens' | 'tool_use' | 'stop_sequence' | 'error' | 'streaming';
|
|
86
86
|
}
|