@lobehub/lobehub 2.0.0-next.38 → 2.0.0-next.39
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/CHANGELOG.md +17 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/dispatcher.test.ts +401 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/tester.test.ts +531 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/urlBuilder.test.ts +349 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/validator.test.ts +492 -0
- package/changelog/v1.json +5 -0
- package/locales/ar/auth.json +45 -1
- package/locales/bg-BG/auth.json +45 -1
- package/locales/de-DE/auth.json +45 -1
- package/locales/en-US/auth.json +45 -1
- package/locales/es-ES/auth.json +45 -1
- package/locales/fa-IR/auth.json +45 -1
- package/locales/fr-FR/auth.json +45 -1
- package/locales/it-IT/auth.json +45 -1
- package/locales/ja-JP/auth.json +45 -1
- package/locales/ko-KR/auth.json +45 -1
- package/locales/nl-NL/auth.json +45 -1
- package/locales/pl-PL/auth.json +45 -1
- package/locales/pt-BR/auth.json +45 -1
- package/locales/ru-RU/auth.json +45 -1
- package/locales/tr-TR/auth.json +45 -1
- package/locales/vi-VN/auth.json +45 -1
- package/locales/zh-CN/auth.json +45 -1
- package/locales/zh-TW/auth.json +45 -1
- package/package.json +1 -1
- package/packages/context-engine/src/processors/MessageCleanup.ts +1 -0
- package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +28 -0
- package/packages/obervability-otel/package.json +3 -1
- package/packages/obervability-otel/src/api.ts +2 -0
- package/packages/obervability-otel/src/trpc/convention.ts +16 -0
- package/packages/obervability-otel/src/trpc/index.test.ts +38 -0
- package/packages/obervability-otel/src/trpc/index.ts +62 -0
- package/packages/obervability-otel/src/trpc/metrics.ts +31 -0
- package/packages/types/src/usage/usageRecord.ts +54 -0
- package/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +10 -1
- package/src/app/[variants]/(main)/profile/usage/Client.tsx +114 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/ModelTable.tsx +175 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/index.tsx +126 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/MonthSpend.tsx +53 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/TodaySpend.tsx +67 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/index.tsx +19 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageTable.tsx +145 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageTrends.tsx +107 -0
- package/src/app/[variants]/(main)/profile/usage/features/components/UsageBarChart.tsx +48 -0
- package/src/app/[variants]/(main)/profile/usage/page.tsx +23 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +3 -3
- package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +37 -14
- package/src/features/Conversation/Messages/Group/Error/index.tsx +1 -1
- package/src/features/Conversation/Messages/Group/GroupChildren.tsx +13 -35
- package/src/features/Conversation/Messages/Group/GroupItem.tsx +43 -0
- package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -2
- package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +1 -1
- package/src/features/Conversation/Messages/Group/Tool/index.tsx +0 -2
- package/src/features/Conversation/Messages/Group/index.tsx +7 -2
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +21 -7
- package/src/features/PluginsUI/Render/BuiltinType/index.tsx +1 -1
- package/src/features/PluginsUI/Render/MCPType/index.tsx +52 -0
- package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +2 -2
- package/src/features/PluginsUI/Render/index.tsx +17 -0
- package/src/libs/mcp/client.ts +3 -2
- package/src/libs/mcp/types.ts +71 -0
- package/src/libs/trpc/lambda/index.ts +5 -2
- package/src/libs/trpc/middleware/openTelemetry.ts +141 -0
- package/src/locales/default/auth.ts +44 -0
- package/src/locales/default/chat.ts +1 -0
- package/src/server/routers/desktop/mcp.ts +1 -3
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/usage.ts +36 -0
- package/src/server/routers/tools/mcp.ts +1 -3
- package/src/server/services/mcp/index.test.ts +28 -15
- package/src/server/services/mcp/index.ts +29 -18
- package/src/server/services/usage/index.test.ts +310 -0
- package/src/server/services/usage/index.ts +164 -0
- package/src/services/chat/contextEngineering.test.ts +4 -0
- package/src/services/mcp.test.ts +7 -1
- package/src/services/mcp.ts +13 -12
- package/src/services/usage.ts +13 -0
- package/src/store/chat/agents/createAgentExecutors.ts +2 -3
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +40 -1
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +13 -5
- package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +3 -3
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +6 -6
- package/src/store/chat/slices/builtinTool/actions/interpreter.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/search.ts +6 -6
- package/src/store/chat/slices/message/actions/publicApi.ts +19 -1
- package/src/store/chat/slices/message/initialState.ts +5 -0
- package/src/store/chat/slices/message/selectors/chat.test.ts +22 -602
- package/src/store/chat/slices/message/selectors/chat.ts +0 -2
- package/src/store/chat/slices/message/selectors/dbMessage.test.ts +51 -0
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +818 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +52 -1
- package/src/store/chat/slices/message/selectors/messageState.ts +2 -0
- package/src/store/chat/slices/plugin/action.test.ts +4 -4
- package/src/store/chat/slices/plugin/actions/index.ts +39 -0
- package/src/store/chat/slices/plugin/actions/internals.ts +83 -0
- package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +188 -0
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +213 -0
- package/src/store/chat/slices/plugin/actions/publicApi.ts +115 -0
- package/src/store/chat/slices/plugin/actions/workflow.ts +121 -0
- package/src/store/chat/store.ts +1 -1
- package/src/store/global/initialState.ts +1 -0
- package/src/store/chat/slices/plugin/action.ts +0 -539
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Attributes, Span } from '@lobechat/observability-otel/api';
|
|
2
|
+
import { SpanKind, SpanStatusCode, diag, trace } from '@lobechat/observability-otel/api';
|
|
3
|
+
import {
|
|
4
|
+
ATTR_ERROR_TYPE,
|
|
5
|
+
ATTR_EXCEPTION_MESSAGE,
|
|
6
|
+
ATTR_EXCEPTION_STACKTRACE,
|
|
7
|
+
DEFAULT_ERROR_CODE,
|
|
8
|
+
DEFAULT_SUCCESS_STATUS,
|
|
9
|
+
TRPCAttribute,
|
|
10
|
+
createAttributesForMetrics,
|
|
11
|
+
getPayloadSize,
|
|
12
|
+
serverDurationHistogram,
|
|
13
|
+
serverRequestSizeHistogram,
|
|
14
|
+
serverRequestsPerRpcHistogram,
|
|
15
|
+
serverResponseSizeHistogram,
|
|
16
|
+
serverResponsesPerRpcHistogram,
|
|
17
|
+
tRPCConventionFromPathAndType,
|
|
18
|
+
} from '@lobechat/observability-otel/trpc';
|
|
19
|
+
import { TRPCError } from '@trpc/server';
|
|
20
|
+
import { env } from 'node:process';
|
|
21
|
+
|
|
22
|
+
import { name } from '../../../../package.json';
|
|
23
|
+
import { trpc } from '../lambda/init';
|
|
24
|
+
|
|
25
|
+
const tracer = trace.getTracer('trpc-server');
|
|
26
|
+
|
|
27
|
+
const recordRpcServerMetrics = ({
|
|
28
|
+
attributes,
|
|
29
|
+
durationMs,
|
|
30
|
+
requestSize,
|
|
31
|
+
responseSize,
|
|
32
|
+
}: {
|
|
33
|
+
attributes: Attributes;
|
|
34
|
+
durationMs: number;
|
|
35
|
+
requestSize?: number;
|
|
36
|
+
responseSize?: number;
|
|
37
|
+
}) => {
|
|
38
|
+
serverDurationHistogram.record(durationMs, attributes);
|
|
39
|
+
serverRequestsPerRpcHistogram.record(1, attributes);
|
|
40
|
+
serverResponsesPerRpcHistogram.record(1, attributes);
|
|
41
|
+
|
|
42
|
+
if (typeof requestSize === 'number') {
|
|
43
|
+
serverRequestSizeHistogram.record(requestSize, attributes);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof responseSize === 'number') {
|
|
47
|
+
serverResponseSizeHistogram.record(responseSize, attributes);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const finalizeSpanWithError = (span: Span, error: unknown) => {
|
|
52
|
+
span.setStatus({
|
|
53
|
+
code: SpanStatusCode.ERROR,
|
|
54
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
span.recordException(error);
|
|
59
|
+
span.setAttribute(ATTR_ERROR_TYPE, error.constructor.name);
|
|
60
|
+
span.setAttribute(ATTR_EXCEPTION_MESSAGE, error.message);
|
|
61
|
+
span.setAttribute(ATTR_EXCEPTION_STACKTRACE, error.stack || '');
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const openTelemetry = trpc.middleware(async ({ path, type, next, getRawInput }) => {
|
|
66
|
+
if (!env.ENABLE_TELEMETRY) {
|
|
67
|
+
diag.debug(name, 'telemetry disabled', env.ENABLE_TELEMETRY);
|
|
68
|
+
|
|
69
|
+
return next();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
diag.debug(name, 'tRPC instrumentation', 'incomingRequest');
|
|
73
|
+
|
|
74
|
+
const spanName = `tRPC ${type.toUpperCase()} ${path}`;
|
|
75
|
+
const baseAttributes = tRPCConventionFromPathAndType(path, type);
|
|
76
|
+
const input = getRawInput();
|
|
77
|
+
const requestSize = getPayloadSize(input);
|
|
78
|
+
|
|
79
|
+
const span = tracer.startSpan(spanName, {
|
|
80
|
+
attributes: baseAttributes,
|
|
81
|
+
kind: SpanKind.SERVER,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const startTimestamp = Date.now();
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const result = await next();
|
|
88
|
+
diag.debug(name, 'tRPC instrumentation', 'requestHandled');
|
|
89
|
+
|
|
90
|
+
const responseSize = getPayloadSize(result.ok ? result.data : result.error);
|
|
91
|
+
|
|
92
|
+
const durationMs = Date.now() - startTimestamp;
|
|
93
|
+
const statusCode = result.ok ? DEFAULT_SUCCESS_STATUS : result.error.code;
|
|
94
|
+
span.setAttribute(TRPCAttribute.RPC_TRPC_STATUS_CODE, statusCode);
|
|
95
|
+
|
|
96
|
+
if (result.ok) {
|
|
97
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
98
|
+
} else {
|
|
99
|
+
finalizeSpanWithError(span, result.error);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
recordRpcServerMetrics({
|
|
103
|
+
attributes: createAttributesForMetrics(baseAttributes, statusCode, {
|
|
104
|
+
[TRPCAttribute.RPC_TRPC_SUCCESS]: result.ok,
|
|
105
|
+
...(result.ok ? undefined : { [ATTR_ERROR_TYPE]: result.error.code }),
|
|
106
|
+
}),
|
|
107
|
+
durationMs,
|
|
108
|
+
requestSize,
|
|
109
|
+
responseSize,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
diag.debug(name, 'tRPC instrumentation', 'metrics recorded');
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
diag.error(name, 'tRPC instrumentation', 'requestError', error);
|
|
117
|
+
|
|
118
|
+
const durationMs = Date.now() - startTimestamp;
|
|
119
|
+
const trpcError = error instanceof TRPCError ? error : undefined;
|
|
120
|
+
const statusCode = trpcError ? trpcError.code : DEFAULT_ERROR_CODE;
|
|
121
|
+
|
|
122
|
+
span.setAttribute(TRPCAttribute.RPC_TRPC_STATUS_CODE, statusCode);
|
|
123
|
+
finalizeSpanWithError(span, error);
|
|
124
|
+
|
|
125
|
+
recordRpcServerMetrics({
|
|
126
|
+
attributes: createAttributesForMetrics(baseAttributes, statusCode, {
|
|
127
|
+
[TRPCAttribute.RPC_TRPC_SUCCESS]: false,
|
|
128
|
+
...(trpcError ? { [ATTR_ERROR_TYPE]: trpcError.code } : undefined),
|
|
129
|
+
}),
|
|
130
|
+
durationMs,
|
|
131
|
+
requestSize,
|
|
132
|
+
responseSize: getPayloadSize(trpcError ? trpcError : error),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
diag.error(name, 'tRPC instrumentation', 'metrics recorded with error', error);
|
|
136
|
+
|
|
137
|
+
throw error;
|
|
138
|
+
} finally {
|
|
139
|
+
span.end();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
@@ -147,5 +147,49 @@ export default {
|
|
|
147
147
|
profile: '个人资料',
|
|
148
148
|
security: '安全',
|
|
149
149
|
stats: '数据统计',
|
|
150
|
+
usage: '用量统计',
|
|
151
|
+
},
|
|
152
|
+
usage: {
|
|
153
|
+
activeModels: {
|
|
154
|
+
modelTable: '模型列表',
|
|
155
|
+
models: '活跃模型',
|
|
156
|
+
providerTable: '提供商列表',
|
|
157
|
+
providers: '活跃提供商',
|
|
158
|
+
table: {
|
|
159
|
+
calls: '调用次数',
|
|
160
|
+
model: '模型',
|
|
161
|
+
provider: '提供商',
|
|
162
|
+
spend: '花费',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
cards: {
|
|
166
|
+
month: {
|
|
167
|
+
modelCalls: '模型调用',
|
|
168
|
+
title: '本月花费',
|
|
169
|
+
},
|
|
170
|
+
today: {
|
|
171
|
+
title: '今日花费',
|
|
172
|
+
yesterday: '昨日',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
table: {
|
|
176
|
+
actions: '操作',
|
|
177
|
+
createdAt: '使用时间',
|
|
178
|
+
inputTokens: '输入 Token',
|
|
179
|
+
model: '模型',
|
|
180
|
+
outputTokens: '输出 Token',
|
|
181
|
+
spend: '花费',
|
|
182
|
+
tps: 'TPS',
|
|
183
|
+
ttft: 'TTFT',
|
|
184
|
+
type: '调用类型',
|
|
185
|
+
},
|
|
186
|
+
trends: {
|
|
187
|
+
spend: '金额',
|
|
188
|
+
tokens: 'Token',
|
|
189
|
+
},
|
|
190
|
+
welcome: {
|
|
191
|
+
model: '模型',
|
|
192
|
+
provider: '提供商',
|
|
193
|
+
},
|
|
150
194
|
},
|
|
151
195
|
};
|
|
@@ -87,13 +87,11 @@ export const mcpRouter = router({
|
|
|
87
87
|
)
|
|
88
88
|
.mutation(async ({ input }) => {
|
|
89
89
|
// Pass the validated params, toolName, and args to the service
|
|
90
|
-
|
|
90
|
+
return await mcpService.callTool(
|
|
91
91
|
{ ...input.params, env: input.env },
|
|
92
92
|
input.toolName,
|
|
93
93
|
input.args,
|
|
94
94
|
);
|
|
95
|
-
|
|
96
|
-
return JSON.stringify(data);
|
|
97
95
|
}),
|
|
98
96
|
|
|
99
97
|
validMcpServerInstallable: mcpProcedure
|
|
@@ -30,6 +30,7 @@ import { sessionGroupRouter } from './sessionGroup';
|
|
|
30
30
|
import { threadRouter } from './thread';
|
|
31
31
|
import { topicRouter } from './topic';
|
|
32
32
|
import { uploadRouter } from './upload';
|
|
33
|
+
import { usageRouter } from './usage';
|
|
33
34
|
import { userRouter } from './user';
|
|
34
35
|
|
|
35
36
|
export const lambdaRouter = router({
|
|
@@ -61,6 +62,7 @@ export const lambdaRouter = router({
|
|
|
61
62
|
thread: threadRouter,
|
|
62
63
|
topic: topicRouter,
|
|
63
64
|
upload: uploadRouter,
|
|
65
|
+
usage: usageRouter,
|
|
64
66
|
user: userRouter,
|
|
65
67
|
});
|
|
66
68
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
|
4
|
+
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
|
5
|
+
import { UsageRecordService } from '@/server/services/usage';
|
|
6
|
+
|
|
7
|
+
const usageProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
|
|
8
|
+
const { ctx } = opts;
|
|
9
|
+
return opts.next({
|
|
10
|
+
ctx: {
|
|
11
|
+
usageRecordService: new UsageRecordService(ctx.serverDB, ctx.userId),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const usageRouter = router({
|
|
17
|
+
findAndGroupByDay: usageProcedure
|
|
18
|
+
.input(
|
|
19
|
+
z.object({
|
|
20
|
+
mo: z.string().optional(),
|
|
21
|
+
}),
|
|
22
|
+
)
|
|
23
|
+
.query(async ({ ctx, input }) => {
|
|
24
|
+
return await ctx.usageRecordService.findAndGroupByDay(input.mo);
|
|
25
|
+
}),
|
|
26
|
+
|
|
27
|
+
findByMonth: usageProcedure
|
|
28
|
+
.input(
|
|
29
|
+
z.object({
|
|
30
|
+
mo: z.string().optional(),
|
|
31
|
+
}),
|
|
32
|
+
)
|
|
33
|
+
.query(async ({ ctx, input }) => {
|
|
34
|
+
return await ctx.usageRecordService.findByMonth(input.mo);
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
@@ -100,8 +100,6 @@ export const mcpRouter = router({
|
|
|
100
100
|
checkStdioEnvironment(input.params);
|
|
101
101
|
|
|
102
102
|
// Pass the validated params, toolName, and args to the service
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return JSON.stringify(data);
|
|
103
|
+
return await mcpService.callTool(input.params, input.toolName, input.args);
|
|
106
104
|
}),
|
|
107
105
|
});
|
|
@@ -33,7 +33,7 @@ describe('MCPService', () => {
|
|
|
33
33
|
args: ['--test'],
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
it('should return
|
|
36
|
+
it('should return MCPToolCallResult when content array is empty', async () => {
|
|
37
37
|
mockClient.callTool.mockResolvedValue({
|
|
38
38
|
content: [],
|
|
39
39
|
isError: false,
|
|
@@ -41,10 +41,12 @@ describe('MCPService', () => {
|
|
|
41
41
|
|
|
42
42
|
const result = await mcpService.callTool(mockParams, 'testTool', '{}');
|
|
43
43
|
|
|
44
|
-
expect(result).
|
|
44
|
+
expect(result.content).toBe('');
|
|
45
|
+
expect(result.success).toBe(true);
|
|
46
|
+
expect(result.state).toEqual({ content: [], isError: false });
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
it('should
|
|
49
|
+
it('should handle null content', async () => {
|
|
48
50
|
mockClient.callTool.mockResolvedValue({
|
|
49
51
|
content: null,
|
|
50
52
|
isError: false,
|
|
@@ -52,10 +54,11 @@ describe('MCPService', () => {
|
|
|
52
54
|
|
|
53
55
|
const result = await mcpService.callTool(mockParams, 'testTool', '{}');
|
|
54
56
|
|
|
55
|
-
expect(result).
|
|
57
|
+
expect(result.content).toBe('');
|
|
58
|
+
expect(result.success).toBe(true);
|
|
56
59
|
});
|
|
57
60
|
|
|
58
|
-
it('should return
|
|
61
|
+
it('should return JSON string when single element contains valid JSON', async () => {
|
|
59
62
|
const jsonData = { message: 'Hello World', status: 'success' };
|
|
60
63
|
mockClient.callTool.mockResolvedValue({
|
|
61
64
|
content: [{ type: 'text', text: JSON.stringify(jsonData) }],
|
|
@@ -64,7 +67,8 @@ describe('MCPService', () => {
|
|
|
64
67
|
|
|
65
68
|
const result = await mcpService.callTool(mockParams, 'testTool', '{}');
|
|
66
69
|
|
|
67
|
-
expect(result).
|
|
70
|
+
expect(result.content).toBe(JSON.stringify(jsonData));
|
|
71
|
+
expect(result.success).toBe(true);
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
it('should return plain text when single element contains non-JSON text', async () => {
|
|
@@ -76,10 +80,12 @@ describe('MCPService', () => {
|
|
|
76
80
|
|
|
77
81
|
const result = await mcpService.callTool(mockParams, 'testTool', '{}');
|
|
78
82
|
|
|
79
|
-
expect(result).toBe(textData);
|
|
83
|
+
expect(result.content).toBe(textData);
|
|
84
|
+
expect(result.success).toBe(true);
|
|
85
|
+
expect(result.state.content).toEqual([{ type: 'text', text: textData }]);
|
|
80
86
|
});
|
|
81
87
|
|
|
82
|
-
it('should return
|
|
88
|
+
it('should return empty content when single element has no text', async () => {
|
|
83
89
|
const contentData = [{ type: 'text', text: '' }];
|
|
84
90
|
mockClient.callTool.mockResolvedValue({
|
|
85
91
|
content: contentData,
|
|
@@ -88,10 +94,12 @@ describe('MCPService', () => {
|
|
|
88
94
|
|
|
89
95
|
const result = await mcpService.callTool(mockParams, 'testTool', '{}');
|
|
90
96
|
|
|
91
|
-
expect(result).
|
|
97
|
+
expect(result.content).toBe('');
|
|
98
|
+
expect(result.success).toBe(true);
|
|
99
|
+
expect(result.state.content).toEqual(contentData);
|
|
92
100
|
});
|
|
93
101
|
|
|
94
|
-
it('should
|
|
102
|
+
it('should join multiple text elements with double newlines', async () => {
|
|
95
103
|
const multipleContent = [
|
|
96
104
|
{ type: 'text', text: 'First message' },
|
|
97
105
|
{ type: 'text', text: 'Second message' },
|
|
@@ -105,11 +113,12 @@ describe('MCPService', () => {
|
|
|
105
113
|
|
|
106
114
|
const result = await mcpService.callTool(mockParams, 'testTool', '{}');
|
|
107
115
|
|
|
108
|
-
|
|
109
|
-
expect(result).
|
|
116
|
+
expect(result.content).toBe('First message\n\nSecond message\n\n{"json": "data"}');
|
|
117
|
+
expect(result.success).toBe(true);
|
|
118
|
+
expect(result.state.content).toEqual(multipleContent);
|
|
110
119
|
});
|
|
111
120
|
|
|
112
|
-
it('should
|
|
121
|
+
it('should join two text elements with double newline', async () => {
|
|
113
122
|
const twoContent = [
|
|
114
123
|
{ type: 'text', text: 'First message' },
|
|
115
124
|
{ type: 'text', text: 'Second message' },
|
|
@@ -122,7 +131,9 @@ describe('MCPService', () => {
|
|
|
122
131
|
|
|
123
132
|
const result = await mcpService.callTool(mockParams, 'testTool', '{}');
|
|
124
133
|
|
|
125
|
-
expect(result).
|
|
134
|
+
expect(result.content).toBe('First message\n\nSecond message');
|
|
135
|
+
expect(result.success).toBe(true);
|
|
136
|
+
expect(result.state.content).toEqual(twoContent);
|
|
126
137
|
});
|
|
127
138
|
|
|
128
139
|
it('should return error result when isError is true', async () => {
|
|
@@ -135,7 +146,9 @@ describe('MCPService', () => {
|
|
|
135
146
|
|
|
136
147
|
const result = await mcpService.callTool(mockParams, 'testTool', '{}');
|
|
137
148
|
|
|
138
|
-
expect(result).
|
|
149
|
+
expect(result.content).toBe('Error occurred');
|
|
150
|
+
expect(result.success).toBe(true);
|
|
151
|
+
expect(result.state).toEqual(errorResult);
|
|
139
152
|
});
|
|
140
153
|
|
|
141
154
|
it('should throw TRPCError when client throws error', async () => {
|
|
@@ -175,29 +175,40 @@ export class MCPService {
|
|
|
175
175
|
loggableParams,
|
|
176
176
|
result,
|
|
177
177
|
);
|
|
178
|
-
const { content, isError } = result;
|
|
179
178
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
179
|
+
// TODO: map more type
|
|
180
|
+
const content = result.content
|
|
181
|
+
? result.content
|
|
182
|
+
.map((item) => {
|
|
183
|
+
switch (item.type) {
|
|
184
|
+
case 'text': {
|
|
185
|
+
return item.text;
|
|
186
|
+
}
|
|
187
|
+
default: {
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
.filter(Boolean)
|
|
193
|
+
.join('\n\n')
|
|
194
|
+
: '';
|
|
195
|
+
|
|
196
|
+
if (result.isError) return { content, state: result, success: true };
|
|
197
|
+
|
|
198
|
+
return { content, state: result, success: true };
|
|
196
199
|
} catch (error) {
|
|
197
200
|
if (error instanceof McpError) {
|
|
198
201
|
const mcpError = error as McpError;
|
|
199
202
|
|
|
200
|
-
return
|
|
203
|
+
return {
|
|
204
|
+
content: mcpError.message,
|
|
205
|
+
error: error,
|
|
206
|
+
state: {
|
|
207
|
+
content: [{ text: mcpError.message, type: 'text' }],
|
|
208
|
+
isError: true,
|
|
209
|
+
},
|
|
210
|
+
success: false,
|
|
211
|
+
};
|
|
201
212
|
}
|
|
202
213
|
|
|
203
214
|
console.error(
|