@lobehub/lobehub 2.0.0-next.196 → 2.0.0-next.198
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 +58 -0
- package/changelog/v1.json +21 -0
- package/package.json +2 -3
- package/packages/database/src/core/getTestDB.ts +50 -0
- package/packages/database/src/models/__tests__/_test_template.ts +1 -1
- package/packages/database/src/models/__tests__/agent.test.ts +1 -1
- package/packages/database/src/models/__tests__/aiModel.test.ts +1 -1
- package/packages/database/src/models/__tests__/aiProvider.test.ts +1 -1
- package/packages/database/src/models/__tests__/apiKey.test.ts +1 -1
- package/packages/database/src/models/__tests__/asyncTask.test.ts +1 -1
- package/packages/database/src/models/__tests__/chatGroup.test.ts +1 -1
- package/packages/database/src/models/__tests__/chunk.test.ts +1 -1
- package/packages/database/src/models/__tests__/document.test.ts +1 -1
- package/packages/database/src/models/__tests__/drizzleMigration.test.ts +1 -1
- package/packages/database/src/models/__tests__/embedding.test.ts +1 -1
- package/packages/database/src/models/__tests__/file.test.ts +1 -1
- package/packages/database/src/models/__tests__/generation.test.ts +1 -1
- package/packages/database/src/models/__tests__/generationBatch.test.ts +1 -1
- package/packages/database/src/models/__tests__/generationTopic.test.ts +1 -1
- package/packages/database/src/models/__tests__/knowledgeBase.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/message.create.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/message.delete.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/message.query.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/message.stats.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/message.thread-query.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/message.update.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/messageWithTask.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/queryWithMessageGroup.perf.test.ts +1 -1
- package/packages/database/src/models/__tests__/messages/queryWithMessageGroup.test.ts +1 -1
- package/packages/database/src/models/__tests__/oauthHandoff.test.ts +1 -1
- package/packages/database/src/models/__tests__/plugin.test.ts +1 -1
- package/packages/database/src/models/__tests__/session.test.ts +1 -1
- package/packages/database/src/models/__tests__/sessionGroup.test.ts +1 -1
- package/packages/database/src/models/__tests__/thread.test.ts +1 -1
- package/packages/database/src/models/__tests__/topicDocument.test.ts +1 -1
- package/packages/database/src/models/__tests__/topics/topic.create.test.ts +1 -1
- package/packages/database/src/models/__tests__/topics/topic.delete.test.ts +1 -1
- package/packages/database/src/models/__tests__/topics/topic.query.test.ts +1 -1
- package/packages/database/src/models/__tests__/topics/topic.stats.test.ts +1 -1
- package/packages/database/src/models/__tests__/topics/topic.update.test.ts +1 -1
- package/packages/database/src/models/__tests__/user.test.ts +1 -1
- package/packages/database/src/models/__tests__/userMemories.test.ts +1 -1
- package/packages/database/src/models/__tests__/userMemoryIdentity.test.ts +1 -1
- package/packages/database/src/models/userMemory/__tests__/context.test.ts +1 -1
- package/packages/database/src/models/userMemory/__tests__/experience.test.ts +1 -1
- package/packages/database/src/models/userMemory/__tests__/identity.test.ts +1 -1
- package/packages/database/src/models/userMemory/__tests__/preference.test.ts +1 -1
- package/packages/database/src/repositories/agentGroup/index.test.ts +1 -1
- package/packages/database/src/repositories/agentMigration/__tests__/agentMigrationRepo.test.ts +1 -1
- package/packages/database/src/repositories/aiInfra/index.test.ts +1 -1
- package/packages/database/src/repositories/compression/index.test.ts +1 -1
- package/packages/database/src/repositories/dataExporter/index.test.ts +1 -1
- package/packages/database/src/repositories/dataImporter/__tests__/index.test.ts +1 -1
- package/packages/database/src/repositories/dataImporter/deprecated/__tests__/index.test.ts +2 -2
- package/packages/database/src/repositories/home/__tests__/index.test.ts +1 -1
- package/packages/database/src/repositories/home/index.test.ts +1 -1
- package/packages/database/src/repositories/knowledge/index.test.ts +1 -1
- package/packages/database/src/repositories/search/index.test.ts +1 -1
- package/packages/database/src/repositories/topicImporter/__tests__/importTopic.test.ts +1 -1
- package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts +1 -1
- package/packages/database/src/server/models/__tests__/adapter.test.ts +2 -2
- package/packages/database/src/server/models/__tests__/user.test.ts +2 -2
- package/packages/database/tests/test-utils.ts +1 -1
- package/packages/model-runtime/src/core/streams/openai/openai.test.ts +155 -0
- package/packages/model-runtime/src/core/streams/openai/openai.ts +26 -1
- package/packages/types/src/export.ts +1 -1
- package/packages/types/src/index.ts +0 -1
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +1 -0
- package/src/features/LibraryModal/AssignKnowledgeBase/List.tsx +12 -13
- package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +1 -0
- package/src/server/routers/lambda/__tests__/file.test.ts +76 -3
- package/src/server/routers/lambda/file.ts +13 -1
- package/src/services/config.ts +2 -16
- package/packages/database/src/core/dbForTest.ts +0 -43
- package/packages/database/src/core/migrations.json +0 -1080
- package/packages/database/src/models/__tests__/_util.ts +0 -30
- package/packages/database/src/repositories/tableViewer/index.test.ts +0 -255
- package/packages/database/src/repositories/tableViewer/index.ts +0 -251
- package/packages/types/src/tableViewer.ts +0 -30
- package/scripts/migrateClientDB/compile-migrations.ts +0 -14
|
@@ -8,7 +8,7 @@ import { AiProviderModelListItem, EnabledAiModel } from 'model-bank';
|
|
|
8
8
|
import { DEFAULT_MODEL_PROVIDER_LIST } from 'model-bank/modelProviders';
|
|
9
9
|
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
10
10
|
|
|
11
|
-
import { getTestDB } from '../../
|
|
11
|
+
import { getTestDB } from '../../core/getTestDB';
|
|
12
12
|
import { LobeChatDatabase } from '../../type';
|
|
13
13
|
import { AiInfraRepos } from './index';
|
|
14
14
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { MessageGroupType } from '@lobechat/types';
|
|
3
3
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
|
-
import { getTestDB } from '../../
|
|
5
|
+
import { getTestDB } from '../../core/getTestDB';
|
|
6
6
|
import { messageGroups, messages } from '../../schemas/message';
|
|
7
7
|
import { topics } from '../../schemas/topic';
|
|
8
8
|
import { users } from '../../schemas/user';
|
|
@@ -2,7 +2,7 @@ import type { ImportPgDataStructure } from '@lobechat/types';
|
|
|
2
2
|
import { eq, inArray } from 'drizzle-orm';
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
|
|
5
|
-
import { getTestDB } from '../../../
|
|
5
|
+
import { getTestDB } from '../../../core/getTestDB';
|
|
6
6
|
import * as Schema from '../../../schemas';
|
|
7
7
|
import { DataImporterRepos } from '../index';
|
|
8
8
|
import agentsData from './fixtures/agents.json';
|
|
@@ -3,7 +3,7 @@ import type { ImporterEntryData } from '@lobechat/types';
|
|
|
3
3
|
import { eq, inArray } from 'drizzle-orm';
|
|
4
4
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { getTestDB } from '../../../../core/getTestDB';
|
|
7
7
|
import {
|
|
8
8
|
agents,
|
|
9
9
|
agentsToSessions,
|
|
@@ -19,7 +19,7 @@ import mockImportData from './fixtures/messages.json';
|
|
|
19
19
|
|
|
20
20
|
const CURRENT_CONFIG_VERSION = 7;
|
|
21
21
|
|
|
22
|
-
const serverDB = await
|
|
22
|
+
const serverDB = await getTestDB();
|
|
23
23
|
|
|
24
24
|
const userId = 'test-user-id';
|
|
25
25
|
let importer: DataImporterRepos;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { eq } from 'drizzle-orm';
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { getTestDB } from '../../../
|
|
4
|
+
import { getTestDB } from '../../../core/getTestDB';
|
|
5
5
|
import * as Schema from '../../../schemas';
|
|
6
6
|
import { HomeRepository } from '../index';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { getTestDB } from '../../
|
|
4
|
+
import { getTestDB } from '../../core/getTestDB';
|
|
5
5
|
import { NewAgent, agents } from '../../schemas/agent';
|
|
6
6
|
import { NewChatGroup, chatGroups } from '../../schemas/chatGroup';
|
|
7
7
|
import { agentsToSessions } from '../../schemas/relations';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { FilesTabs } from '@lobechat/types';
|
|
3
3
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
|
-
import { getTestDB } from '../../
|
|
5
|
+
import { getTestDB } from '../../core/getTestDB';
|
|
6
6
|
import { NewDocument, documents } from '../../schemas/file';
|
|
7
7
|
import { NewFile, files } from '../../schemas/file';
|
|
8
8
|
import { users } from '../../schemas/user';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { getTestDB } from '../../
|
|
4
|
+
import { getTestDB } from '../../core/getTestDB';
|
|
5
5
|
import { NewAgent, agents } from '../../schemas/agent';
|
|
6
6
|
import { NewFile, files } from '../../schemas/file';
|
|
7
7
|
import { messages } from '../../schemas/message';
|
|
@@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import { getTestDB } from '../../../
|
|
7
|
+
import { getTestDB } from '../../../core/getTestDB';
|
|
8
8
|
import { agents, messagePlugins, messages, topics, users } from '../../../schemas';
|
|
9
9
|
import { LobeChatDatabase } from '../../../type';
|
|
10
10
|
import { TopicImporterRepo } from '../index';
|
package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { getTestDB } from '../../../
|
|
4
|
+
import { getTestDB } from '../../../core/getTestDB';
|
|
5
5
|
import { messages } from '../../../schemas/message';
|
|
6
6
|
import { topics } from '../../../schemas/topic';
|
|
7
7
|
import { users } from '../../../schemas/user';
|
|
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
|
3
3
|
|
|
4
4
|
import { DrizzleAdapter } from '@/libs/oidc-provider/adapter';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { getTestDB } from '../../../core/getTestDB';
|
|
7
7
|
import { users } from '../../../schemas';
|
|
8
8
|
import {
|
|
9
9
|
oidcAccessTokens,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
oidcSessions,
|
|
15
15
|
} from '../../../schemas/oidc';
|
|
16
16
|
|
|
17
|
-
let serverDB = await
|
|
17
|
+
let serverDB = await getTestDB();
|
|
18
18
|
|
|
19
19
|
// Test data
|
|
20
20
|
const testModelName = 'Session';
|
|
@@ -7,12 +7,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
7
7
|
|
|
8
8
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { getTestDB } from '../../../core/getTestDB';
|
|
11
11
|
import { SessionModel } from '../../../models/session';
|
|
12
12
|
import { UserModel, UserNotFoundError } from '../../../models/user';
|
|
13
13
|
import { UserSettingsItem, nextauthAccounts, userSettings, users } from '../../../schemas';
|
|
14
14
|
|
|
15
|
-
let serverDB = await
|
|
15
|
+
let serverDB = await getTestDB();
|
|
16
16
|
|
|
17
17
|
const userId = 'user-db';
|
|
18
18
|
const userEmail = 'user@example.com';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from '../src/
|
|
1
|
+
export * from '../src/core/getTestDB';
|
|
@@ -1048,6 +1048,161 @@ describe('OpenAIStream', () => {
|
|
|
1048
1048
|
].map((i) => `${i}\n`),
|
|
1049
1049
|
);
|
|
1050
1050
|
});
|
|
1051
|
+
|
|
1052
|
+
it('should handle OpenRouter tool calls with thoughtSignature (for Gemini models)', async () => {
|
|
1053
|
+
// OpenRouter returns thoughtSignature in tool_calls for Gemini models
|
|
1054
|
+
// This is required for preserving reasoning blocks across turns
|
|
1055
|
+
// Ref: https://openrouter.ai/docs/guides/best-practices/reasoning-tokens
|
|
1056
|
+
const mockOpenAIStream = new ReadableStream({
|
|
1057
|
+
start(controller) {
|
|
1058
|
+
controller.enqueue({
|
|
1059
|
+
choices: [
|
|
1060
|
+
{
|
|
1061
|
+
delta: {
|
|
1062
|
+
tool_calls: [
|
|
1063
|
+
{
|
|
1064
|
+
function: { name: 'github__get_me', arguments: '{}' },
|
|
1065
|
+
id: 'call_123',
|
|
1066
|
+
index: 0,
|
|
1067
|
+
type: 'function',
|
|
1068
|
+
// OpenRouter adds thoughtSignature for Gemini 3 models
|
|
1069
|
+
thoughtSignature: 'ErEDCq4DAdHtim...',
|
|
1070
|
+
},
|
|
1071
|
+
],
|
|
1072
|
+
},
|
|
1073
|
+
index: 0,
|
|
1074
|
+
},
|
|
1075
|
+
],
|
|
1076
|
+
id: 'or-123',
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
controller.close();
|
|
1080
|
+
},
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
const onToolCallMock = vi.fn();
|
|
1084
|
+
|
|
1085
|
+
const protocolStream = OpenAIStream(mockOpenAIStream, {
|
|
1086
|
+
callbacks: {
|
|
1087
|
+
onToolsCalling: onToolCallMock,
|
|
1088
|
+
},
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
const decoder = new TextDecoder();
|
|
1092
|
+
const chunks = [];
|
|
1093
|
+
|
|
1094
|
+
// @ts-ignore
|
|
1095
|
+
for await (const chunk of protocolStream) {
|
|
1096
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
expect(chunks).toEqual([
|
|
1100
|
+
'id: or-123\n',
|
|
1101
|
+
'event: tool_calls\n',
|
|
1102
|
+
// thoughtSignature should be preserved in the output
|
|
1103
|
+
`data: [{"function":{"arguments":"{}","name":"github__get_me"},"id":"call_123","index":0,"type":"function","thoughtSignature":"ErEDCq4DAdHtim..."}]\n\n`,
|
|
1104
|
+
]);
|
|
1105
|
+
|
|
1106
|
+
// Verify the callback receives thoughtSignature
|
|
1107
|
+
expect(onToolCallMock).toHaveBeenCalledWith({
|
|
1108
|
+
chunk: [
|
|
1109
|
+
{
|
|
1110
|
+
function: { arguments: '{}', name: 'github__get_me' },
|
|
1111
|
+
id: 'call_123',
|
|
1112
|
+
index: 0,
|
|
1113
|
+
thoughtSignature: 'ErEDCq4DAdHtim...',
|
|
1114
|
+
type: 'function',
|
|
1115
|
+
},
|
|
1116
|
+
],
|
|
1117
|
+
toolsCalling: [
|
|
1118
|
+
{
|
|
1119
|
+
function: { arguments: '{}', name: 'github__get_me' },
|
|
1120
|
+
id: 'call_123',
|
|
1121
|
+
thoughtSignature: 'ErEDCq4DAdHtim...',
|
|
1122
|
+
type: 'function',
|
|
1123
|
+
},
|
|
1124
|
+
],
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
it('should NOT include thoughtSignature in output when not present in tool call', async () => {
|
|
1129
|
+
// Standard tool calls without thoughtSignature should not include the field
|
|
1130
|
+
const mockOpenAIStream = new ReadableStream({
|
|
1131
|
+
start(controller) {
|
|
1132
|
+
controller.enqueue({
|
|
1133
|
+
choices: [
|
|
1134
|
+
{
|
|
1135
|
+
delta: {
|
|
1136
|
+
tool_calls: [
|
|
1137
|
+
{
|
|
1138
|
+
function: { name: 'search', arguments: '{"query":"test"}' },
|
|
1139
|
+
id: 'call_456',
|
|
1140
|
+
index: 0,
|
|
1141
|
+
type: 'function',
|
|
1142
|
+
// No thoughtSignature field
|
|
1143
|
+
},
|
|
1144
|
+
],
|
|
1145
|
+
},
|
|
1146
|
+
index: 0,
|
|
1147
|
+
},
|
|
1148
|
+
],
|
|
1149
|
+
id: 'standard-123',
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
controller.close();
|
|
1153
|
+
},
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
const onToolCallMock = vi.fn();
|
|
1157
|
+
|
|
1158
|
+
const protocolStream = OpenAIStream(mockOpenAIStream, {
|
|
1159
|
+
callbacks: {
|
|
1160
|
+
onToolsCalling: onToolCallMock,
|
|
1161
|
+
},
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
const decoder = new TextDecoder();
|
|
1165
|
+
const chunks = [];
|
|
1166
|
+
|
|
1167
|
+
// @ts-ignore
|
|
1168
|
+
for await (const chunk of protocolStream) {
|
|
1169
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
expect(chunks).toEqual([
|
|
1173
|
+
'id: standard-123\n',
|
|
1174
|
+
'event: tool_calls\n',
|
|
1175
|
+
// thoughtSignature should NOT be in the output
|
|
1176
|
+
`data: [{"function":{"arguments":"{\\"query\\":\\"test\\"}","name":"search"},"id":"call_456","index":0,"type":"function"}]\n\n`,
|
|
1177
|
+
]);
|
|
1178
|
+
|
|
1179
|
+
// Verify the callback does NOT receive thoughtSignature
|
|
1180
|
+
expect(onToolCallMock).toHaveBeenCalledWith({
|
|
1181
|
+
chunk: [
|
|
1182
|
+
{
|
|
1183
|
+
function: { arguments: '{"query":"test"}', name: 'search' },
|
|
1184
|
+
id: 'call_456',
|
|
1185
|
+
index: 0,
|
|
1186
|
+
// thoughtSignature should not be present
|
|
1187
|
+
type: 'function',
|
|
1188
|
+
},
|
|
1189
|
+
],
|
|
1190
|
+
toolsCalling: [
|
|
1191
|
+
{
|
|
1192
|
+
function: { arguments: '{"query":"test"}', name: 'search' },
|
|
1193
|
+
id: 'call_456',
|
|
1194
|
+
// thoughtSignature should not be present
|
|
1195
|
+
type: 'function',
|
|
1196
|
+
},
|
|
1197
|
+
],
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
// Verify thoughtSignature is not in the chunk
|
|
1201
|
+
expect(onToolCallMock.mock.calls[0][0].chunk[0]).not.toHaveProperty('thoughtSignature');
|
|
1202
|
+
expect(onToolCallMock.mock.calls[0][0].toolsCalling[0]).not.toHaveProperty(
|
|
1203
|
+
'thoughtSignature',
|
|
1204
|
+
);
|
|
1205
|
+
});
|
|
1051
1206
|
});
|
|
1052
1207
|
|
|
1053
1208
|
describe('Reasoning', () => {
|
|
@@ -20,6 +20,23 @@ import {
|
|
|
20
20
|
generateToolCallId,
|
|
21
21
|
} from '../protocol';
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Extended type for OpenAI tool calls that includes provider-specific extensions
|
|
25
|
+
* like OpenRouter's thoughtSignature for Gemini models
|
|
26
|
+
*/
|
|
27
|
+
type OpenAIExtendedToolCall = OpenAI.ChatCompletionChunk.Choice.Delta.ToolCall & {
|
|
28
|
+
thoughtSignature?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Type guard to check if a tool call has thoughtSignature
|
|
33
|
+
*/
|
|
34
|
+
const hasThoughtSignature = (
|
|
35
|
+
toolCall: OpenAI.ChatCompletionChunk.Choice.Delta.ToolCall,
|
|
36
|
+
): toolCall is OpenAIExtendedToolCall => {
|
|
37
|
+
return 'thoughtSignature' in toolCall && typeof toolCall.thoughtSignature === 'string';
|
|
38
|
+
};
|
|
39
|
+
|
|
23
40
|
// Process markdown base64 images: extract URLs and clean text in one pass
|
|
24
41
|
const processMarkdownBase64Images = (text: string): { cleanedText: string; urls: string[] } => {
|
|
25
42
|
if (!text) return { cleanedText: text, urls: [] };
|
|
@@ -150,7 +167,7 @@ const transformOpenAIStream = (
|
|
|
150
167
|
};
|
|
151
168
|
}
|
|
152
169
|
|
|
153
|
-
|
|
170
|
+
const baseData: StreamToolCallChunkData = {
|
|
154
171
|
function: {
|
|
155
172
|
arguments: value.function?.arguments ?? '',
|
|
156
173
|
name: value.function?.name ?? null,
|
|
@@ -170,6 +187,14 @@ const transformOpenAIStream = (
|
|
|
170
187
|
index: typeof value.index !== 'undefined' ? value.index : index,
|
|
171
188
|
type: value.type || 'function',
|
|
172
189
|
};
|
|
190
|
+
|
|
191
|
+
// OpenRouter returns thoughtSignature in tool_calls for Gemini models (e.g. gemini-3-flash-preview)
|
|
192
|
+
// [{"id":"call_123","type":"function","function":{"name":"get_weather","arguments":"{}"},"thoughtSignature":"abc123"}]
|
|
193
|
+
if (hasThoughtSignature(value)) {
|
|
194
|
+
baseData.thoughtSignature = value.thoughtSignature;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return baseData;
|
|
173
198
|
}),
|
|
174
199
|
id: chunk.id,
|
|
175
200
|
type: 'tool_calls',
|
|
@@ -52,6 +52,7 @@ const Token = memo<TokenTagProps>(({ total: messageString }) => {
|
|
|
52
52
|
chatConfigByIdSelectors.getEnableHistoryCountById(agentId)(s),
|
|
53
53
|
// need to re-render by search mode
|
|
54
54
|
chatConfigByIdSelectors.isEnableSearchById(agentId)(s),
|
|
55
|
+
chatConfigByIdSelectors.getUseModelBuiltinSearchById(agentId)(s),
|
|
55
56
|
]);
|
|
56
57
|
|
|
57
58
|
const maxTokens = useModelContextWindowTokens(model, provider);
|
|
@@ -114,19 +114,18 @@ export const List = memo(() => {
|
|
|
114
114
|
totalCount={data!.length}
|
|
115
115
|
/>
|
|
116
116
|
) : (
|
|
117
|
-
<div style={{
|
|
118
|
-
<div style={{
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
</div>
|
|
117
|
+
<div style={{ height: '100%', position: 'relative' }}>
|
|
118
|
+
<div style={{ inset: 0, position: 'absolute' }}>
|
|
119
|
+
<VirtuosoMasonry
|
|
120
|
+
ItemContent={MasonryItemWrapper}
|
|
121
|
+
columnCount={columnCount}
|
|
122
|
+
context={masonryContext}
|
|
123
|
+
data={data || []}
|
|
124
|
+
style={{
|
|
125
|
+
gap: '16px',
|
|
126
|
+
height: '100%',
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
130
129
|
</div>
|
|
131
130
|
</div>
|
|
132
131
|
)}
|
|
@@ -245,7 +245,35 @@ describe('fileRouter', () => {
|
|
|
245
245
|
);
|
|
246
246
|
});
|
|
247
247
|
|
|
248
|
-
it('should
|
|
248
|
+
it('should fallback to input size when getFileMetadata fails', async () => {
|
|
249
|
+
mockFileModelCheckHash.mockResolvedValue({ isExist: false });
|
|
250
|
+
mockFileModelCreate.mockResolvedValue({ id: 'new-file-id' });
|
|
251
|
+
mockFileServiceGetFileMetadata.mockRejectedValue(new Error('File not found in S3'));
|
|
252
|
+
|
|
253
|
+
const result = await caller.createFile({
|
|
254
|
+
hash: 'test-hash',
|
|
255
|
+
fileType: 'text',
|
|
256
|
+
name: 'test.txt',
|
|
257
|
+
size: 100,
|
|
258
|
+
url: 'files/non-existent.txt',
|
|
259
|
+
metadata: {},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(result).toEqual({
|
|
263
|
+
id: 'new-file-id',
|
|
264
|
+
url: 'https://lobehub.com/f/new-file-id',
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Verify create was called with input size as fallback
|
|
268
|
+
expect(mockFileModelCreate).toHaveBeenCalledWith(
|
|
269
|
+
expect.objectContaining({
|
|
270
|
+
size: 100,
|
|
271
|
+
}),
|
|
272
|
+
true,
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should throw error when getFileMetadata fails and input size is less than 1', async () => {
|
|
249
277
|
mockFileModelCheckHash.mockResolvedValue({ isExist: false });
|
|
250
278
|
mockFileServiceGetFileMetadata.mockRejectedValue(new Error('File not found in S3'));
|
|
251
279
|
|
|
@@ -254,11 +282,56 @@ describe('fileRouter', () => {
|
|
|
254
282
|
hash: 'test-hash',
|
|
255
283
|
fileType: 'text',
|
|
256
284
|
name: 'test.txt',
|
|
257
|
-
size:
|
|
285
|
+
size: 0,
|
|
258
286
|
url: 'files/non-existent.txt',
|
|
259
287
|
metadata: {},
|
|
260
288
|
}),
|
|
261
|
-
).rejects.toThrow('File
|
|
289
|
+
).rejects.toThrow('File size must be at least 1 byte');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should use input size when getFileMetadata returns contentLength less than 1', async () => {
|
|
293
|
+
mockFileModelCheckHash.mockResolvedValue({ isExist: false });
|
|
294
|
+
mockFileModelCreate.mockResolvedValue({ id: 'new-file-id' });
|
|
295
|
+
mockFileServiceGetFileMetadata.mockResolvedValue({
|
|
296
|
+
contentLength: 0,
|
|
297
|
+
contentType: 'text/plain',
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
await caller.createFile({
|
|
301
|
+
hash: 'test-hash',
|
|
302
|
+
fileType: 'text',
|
|
303
|
+
name: 'test.txt',
|
|
304
|
+
size: 100,
|
|
305
|
+
url: 'files/test.txt',
|
|
306
|
+
metadata: {},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Verify create was called with input size since contentLength < 1
|
|
310
|
+
expect(mockFileModelCreate).toHaveBeenCalledWith(
|
|
311
|
+
expect.objectContaining({
|
|
312
|
+
size: 100,
|
|
313
|
+
}),
|
|
314
|
+
true,
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should throw error when both getFileMetadata contentLength and input size are less than 1', async () => {
|
|
319
|
+
mockFileModelCheckHash.mockResolvedValue({ isExist: false });
|
|
320
|
+
mockFileServiceGetFileMetadata.mockResolvedValue({
|
|
321
|
+
contentLength: 0,
|
|
322
|
+
contentType: 'text/plain',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
await expect(
|
|
326
|
+
caller.createFile({
|
|
327
|
+
hash: 'test-hash',
|
|
328
|
+
fileType: 'text',
|
|
329
|
+
name: 'test.txt',
|
|
330
|
+
size: 0,
|
|
331
|
+
url: 'files/test.txt',
|
|
332
|
+
metadata: {},
|
|
333
|
+
}),
|
|
334
|
+
).rejects.toThrow('File size must be at least 1 byte');
|
|
262
335
|
});
|
|
263
336
|
});
|
|
264
337
|
|
|
@@ -64,7 +64,19 @@ export const fileRouter = router({
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
let actualSize = input.size;
|
|
68
|
+
try {
|
|
69
|
+
const { contentLength } = await ctx.fileService.getFileMetadata(input.url);
|
|
70
|
+
if (contentLength >= 1) {
|
|
71
|
+
actualSize = contentLength;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// If metadata fetch fails, use original size from input
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (actualSize < 1) {
|
|
78
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'File size must be at least 1 byte' });
|
|
79
|
+
}
|
|
68
80
|
|
|
69
81
|
const { id } = await ctx.fileModel.create(
|
|
70
82
|
{
|
package/src/services/config.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { exportService } from './export';
|
|
|
8
8
|
|
|
9
9
|
class ConfigService {
|
|
10
10
|
exportAll = async () => {
|
|
11
|
-
const { data, url } = await exportService.exportData();
|
|
11
|
+
const { data, url, schemaHash } = await exportService.exportData();
|
|
12
12
|
const filename = `${dayjs().format('YYYY-MM-DD-hh-mm')}_${BRANDING_NAME}-data.json`;
|
|
13
13
|
|
|
14
14
|
// if url exists, means export data from server and upload the data to S3
|
|
@@ -18,24 +18,10 @@ class ConfigService {
|
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
const result = await this.createDataStructure(data, 'postgres');
|
|
21
|
+
const result: ImportPgDataStructure = { data, mode: 'postgres', schemaHash };
|
|
23
22
|
|
|
24
23
|
exportJSONFile(result, filename);
|
|
25
24
|
};
|
|
26
|
-
|
|
27
|
-
private createDataStructure = async (
|
|
28
|
-
data: any,
|
|
29
|
-
mode: 'pglite' | 'postgres',
|
|
30
|
-
): Promise<ImportPgDataStructure> => {
|
|
31
|
-
const { default: json } = await import('@/database/core/migrations.json');
|
|
32
|
-
const latestHash = json.at(-1)?.hash;
|
|
33
|
-
if (!latestHash) {
|
|
34
|
-
throw new Error('Not find database sql hash');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return { data, mode, schemaHash: latestHash };
|
|
38
|
-
};
|
|
39
25
|
}
|
|
40
26
|
|
|
41
27
|
export const configService = new ConfigService();
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Pool as NeonPool, neonConfig } from '@neondatabase/serverless';
|
|
2
|
-
import { drizzle as neonDrizzle } from 'drizzle-orm/neon-serverless';
|
|
3
|
-
import * as migrator from 'drizzle-orm/neon-serverless/migrator';
|
|
4
|
-
import { drizzle as nodeDrizzle } from 'drizzle-orm/node-postgres';
|
|
5
|
-
import * as nodeMigrator from 'drizzle-orm/node-postgres/migrator';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { Pool as NodePool } from 'pg';
|
|
8
|
-
import ws from 'ws';
|
|
9
|
-
|
|
10
|
-
import { serverDBEnv } from '@/config/db';
|
|
11
|
-
|
|
12
|
-
import * as schema from '../schemas';
|
|
13
|
-
|
|
14
|
-
const migrationsFolder = join(__dirname, '../../migrations');
|
|
15
|
-
|
|
16
|
-
export const getTestDBInstance = async () => {
|
|
17
|
-
let connectionString = serverDBEnv.DATABASE_TEST_URL;
|
|
18
|
-
|
|
19
|
-
if (!connectionString) {
|
|
20
|
-
throw new Error(`You are try to use database, but "DATABASE_TEST_URL" is not set correctly`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (serverDBEnv.DATABASE_DRIVER === 'node') {
|
|
24
|
-
const client = new NodePool({ connectionString });
|
|
25
|
-
|
|
26
|
-
const db = nodeDrizzle(client, { schema });
|
|
27
|
-
|
|
28
|
-
await nodeMigrator.migrate(db, { migrationsFolder });
|
|
29
|
-
|
|
30
|
-
return db;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// https://github.com/neondatabase/serverless/blob/main/CONFIG.md#websocketconstructor-typeof-websocket--undefined
|
|
34
|
-
neonConfig.webSocketConstructor = ws;
|
|
35
|
-
|
|
36
|
-
const client = new NeonPool({ connectionString });
|
|
37
|
-
|
|
38
|
-
const db = neonDrizzle(client, { schema });
|
|
39
|
-
|
|
40
|
-
await migrator.migrate(db, { migrationsFolder });
|
|
41
|
-
|
|
42
|
-
return db;
|
|
43
|
-
};
|