Skip to content

Commit 15ebcb4

Browse files
authored
šŸ› fix: fix mcp server connect issue and refactor web search implement (#9694)
* add * clean * refactor * refactor * add test * fix style * refactor to improve search performance * refactor types * refactor types * refactor types * fix mcp retry issue * add more tests * fix test and types * fix test * fix desktop remote streamable http * add local * fix tests * update
1 parent cb98604 commit 15ebcb4

File tree

94 files changed

+1318
-558
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+1318
-558
lines changed

ā€Žpackages/const/src/index.tsā€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './auth';
22
export * from './branding';
33
export * from './currency';
44
export * from './desktop';
5+
export * from './discover';
56
export * from './guide';
67
export * from './layoutTokens';
78
export * from './message';

ā€Žpackages/context-engine/src/__tests__/pipeline.test.tsā€Ž

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,6 @@ describe('ContextEngine', () => {
243243

244244
expect(result.stats.processedCount).toBe(2);
245245
expect(result.stats.totalDuration).toBeGreaterThanOrEqual(20);
246-
expect(result.stats.processorDurations.p1).toBeGreaterThanOrEqual(10);
247-
expect(result.stats.processorDurations.p2).toBeGreaterThanOrEqual(20);
248246
});
249247

250248
it('should stop processing when aborted', async () => {

ā€Žpackages/database/src/models/plugin.tsā€Ž

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
import { LobeTool } from '@lobechat/types';
12
import { and, desc, eq } from 'drizzle-orm';
23

3-
import { LobeChatDatabase } from '../type';
4-
import { LobeTool } from '@/types/tool';
5-
64
import { InstalledPluginItem, NewInstalledPlugin, userInstalledPlugins } from '../schemas';
5+
import { LobeChatDatabase } from '../type';
76

87
export class PluginModel {
98
private userId: string;

ā€Žpackages/model-runtime/src/index.tsā€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export { LobeZeroOneAI } from './providers/zeroone';
3434
export { LobeZhipuAI } from './providers/zhipu';
3535
export * from './types';
3636
export * from './types/error';
37+
export { consumeStreamUntilDone } from './utils/consumeStream';
3738
export { AgentRuntimeError } from './utils/createError';
3839
export { getModelPropertyWithFallback } from './utils/getFallbackModelProperty';
3940
export { getModelPricing } from './utils/getModelPricing';

ā€Žpackages/model-runtime/src/types/chat.tsā€Ž

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ModelSpeed, ModelTokensUsage, ModelUsage } from '@/types/message';
1+
import { ModelSpeed, ModelTokensUsage, ModelUsage } from '@lobechat/types';
22

33
import { MessageToolCall, MessageToolCallChunk } from './toolsCalling';
44

@@ -207,6 +207,9 @@ export interface ChatStreamCallbacks {
207207
onThinking?: (content: string) => Promise<void> | void;
208208
onToolsCalling?: (data: {
209209
chunk: MessageToolCallChunk[];
210+
/**
211+
* full tools calling array
212+
*/
210213
toolsCalling: MessageToolCall[];
211214
}) => Promise<void> | void;
212215
onUsage?: (usage: ModelTokensUsage) => Promise<void> | void;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
import { consumeStreamUntilDone } from './consumeStream';
4+
5+
describe('consumeStreamUntilDone', () => {
6+
it('should consume a stream completely', async () => {
7+
const chunks = ['chunk1', 'chunk2', 'chunk3'];
8+
const stream = new ReadableStream({
9+
start(controller) {
10+
for (const chunk of chunks) {
11+
controller.enqueue(new TextEncoder().encode(chunk));
12+
}
13+
controller.close();
14+
},
15+
});
16+
17+
const response = new Response(stream);
18+
await consumeStreamUntilDone(response);
19+
20+
// Stream should be consumed (reader should be locked and released)
21+
expect(response.body?.locked).toBe(false);
22+
});
23+
24+
it('should handle response without body', async () => {
25+
const response = new Response(null);
26+
await expect(consumeStreamUntilDone(response)).resolves.toBeUndefined();
27+
});
28+
29+
it('should release reader lock even when stream errors occur', async () => {
30+
const stream = new ReadableStream({
31+
start(controller) {
32+
controller.error(new Error('Stream error'));
33+
},
34+
});
35+
36+
const response = new Response(stream);
37+
38+
await expect(consumeStreamUntilDone(response)).rejects.toThrow('Stream error');
39+
40+
// Reader lock should still be released
41+
expect(response.body?.locked).toBe(false);
42+
});
43+
44+
it('should handle empty stream', async () => {
45+
const stream = new ReadableStream({
46+
start(controller) {
47+
controller.close();
48+
},
49+
});
50+
51+
const response = new Response(stream);
52+
await expect(consumeStreamUntilDone(response)).resolves.toBeUndefined();
53+
});
54+
55+
it('should consume stream with large number of chunks', async () => {
56+
const chunkCount = 100;
57+
const stream = new ReadableStream({
58+
start(controller) {
59+
for (let i = 0; i < chunkCount; i++) {
60+
controller.enqueue(new TextEncoder().encode(`chunk${i}`));
61+
}
62+
controller.close();
63+
},
64+
});
65+
66+
const response = new Response(stream);
67+
await expect(consumeStreamUntilDone(response)).resolves.toBeUndefined();
68+
expect(response.body?.locked).toBe(false);
69+
});
70+
71+
it('should ensure reader.releaseLock is called', async () => {
72+
const stream = new ReadableStream({
73+
start(controller) {
74+
controller.enqueue(new TextEncoder().encode('test'));
75+
controller.close();
76+
},
77+
});
78+
79+
const response = new Response(stream);
80+
const reader = response.body!.getReader();
81+
const releaseLockSpy = vi.spyOn(reader, 'releaseLock');
82+
reader.releaseLock(); // Release the lock we just acquired
83+
84+
await consumeStreamUntilDone(response);
85+
86+
// The function should acquire a new reader and release it
87+
expect(releaseLockSpy).toHaveBeenCalled();
88+
});
89+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Consumes a Response stream completely to ensure all callbacks are executed
3+
* @param response - The Response object with a ReadableStream body
4+
* @returns Promise that resolves when the stream is fully consumed
5+
*
6+
* @example
7+
* ```ts
8+
* const response = await modelRuntime.chat(payload, {
9+
* callback: {
10+
* onText: async (text) => {
11+
* await saveToDatabase(text);
12+
* }
13+
* }
14+
* });
15+
*
16+
* // Ensure all callbacks complete before proceeding
17+
* await consumeStreamUntilDone(response);
18+
* ```
19+
*/
20+
export async function consumeStreamUntilDone(response: Response): Promise<void> {
21+
if (!response.body) {
22+
return;
23+
}
24+
25+
const reader = response.body.getReader();
26+
try {
27+
// eslint-disable-next-line no-constant-condition
28+
while (true) {
29+
const { done } = await reader.read();
30+
if (done) break;
31+
}
32+
} finally {
33+
reader.releaseLock();
34+
}
35+
}

ā€Žpackages/types/src/aiProvider.tsā€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export type ResponseAnimation =
66
| {
77
speed?: number;
88
text?: ResponseAnimationStyle;
9-
toolsCalling?: ResponseAnimationStyle;
109
}
1110
| ResponseAnimationStyle;
1211

ā€Žpackages/types/src/index.tsā€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export * from './knowledgeBase';
1616
export * from './llm';
1717
export * from './message';
1818
export * from './meta';
19+
export * from './plugins';
1920
export * from './rag';
2021
export * from './search';
2122
export * from './serverConfig';

ā€Žpackages/types/src/message/tools.tsā€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { IPluginErrorType } from '@lobehub/chat-plugin-sdk';
22
import type { PartialDeep } from 'type-fest';
33
import { z } from 'zod';
44

5-
import { LobeToolRenderType } from '@/types/tool';
5+
import { LobeToolRenderType } from '../tool';
66

77
export interface ChatPluginPayload {
88
apiName: string;

0 commit comments

Comments
Ā (0)