@jskit-ai/assistant-runtime 0.1.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/package.descriptor.mjs +136 -0
- package/package.json +21 -0
- package/src/client/components/AssistantSettingsClientElement.vue +204 -0
- package/src/client/components/AssistantSurfaceClientElement.vue +19 -0
- package/src/client/composables/useAssistantRuntime.js +759 -0
- package/src/client/index.js +4 -0
- package/src/client/providers/AssistantClientProvider.js +16 -0
- package/src/server/AssistantProvider.js +152 -0
- package/src/server/actionIds.js +9 -0
- package/src/server/actions.js +151 -0
- package/src/server/inputValidators.js +41 -0
- package/src/server/registerRoutes.js +450 -0
- package/src/server/repositories/assistantConfigRepository.js +148 -0
- package/src/server/repositories/conversationsRepository.js +263 -0
- package/src/server/repositories/messagesRepository.js +166 -0
- package/src/server/services/assistantConfigService.js +132 -0
- package/src/server/services/chatService.js +1048 -0
- package/src/server/services/transcriptService.js +331 -0
- package/src/server/support/assistantServerConfig.js +106 -0
- package/src/server/support/createSurfaceAwareToolCatalog.js +64 -0
- package/src/shared/assistantRuntimeConfig.js +7 -0
- package/src/shared/assistantSurfaces.js +97 -0
- package/src/shared/index.js +7 -0
- package/templates/migrations/assistant_config_initial.cjs +27 -0
- package/templates/migrations/assistant_transcripts_initial.cjs +58 -0
- package/test/assistantServerConfig.test.js +72 -0
- package/test/assistantSurfaces.test.js +50 -0
- package/test/createSurfaceAwareToolCatalog.test.js +77 -0
- package/test/lazyAppConfig.test.js +248 -0
- package/test/packageDescriptor.test.js +34 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
2
|
+
import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
3
|
+
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
4
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
+
import {
|
|
6
|
+
parseJsonObject,
|
|
7
|
+
resolveInsertedId,
|
|
8
|
+
stringifyJsonObject,
|
|
9
|
+
toIso
|
|
10
|
+
} from "@jskit-ai/assistant-core/server";
|
|
11
|
+
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
12
|
+
|
|
13
|
+
function normalizeWorkspaceId(value) {
|
|
14
|
+
return parsePositiveInteger(value) || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeRequiredSurfaceId(value) {
|
|
18
|
+
const normalizedSurfaceId = normalizeSurfaceId(value);
|
|
19
|
+
if (!normalizedSurfaceId) {
|
|
20
|
+
throw new TypeError("conversationsRepository requires surfaceId.");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return normalizedSurfaceId;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function applyWorkspaceScope(query, columnName, workspaceId) {
|
|
27
|
+
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
|
|
28
|
+
if (normalizedWorkspaceId) {
|
|
29
|
+
return query.where(columnName, normalizedWorkspaceId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return query.whereNull(columnName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function mapConversationRow(row = {}) {
|
|
36
|
+
return {
|
|
37
|
+
id: Number(row.id),
|
|
38
|
+
workspaceId: normalizeWorkspaceId(row.workspace_id),
|
|
39
|
+
title: String(row.title || "New conversation"),
|
|
40
|
+
createdByUserId: row.created_by_user_id == null ? null : Number(row.created_by_user_id),
|
|
41
|
+
status: String(row.status || "active"),
|
|
42
|
+
provider: String(row.provider || ""),
|
|
43
|
+
model: String(row.model || ""),
|
|
44
|
+
surfaceId: String(row.surface_id || ""),
|
|
45
|
+
startedAt: toIso(row.started_at),
|
|
46
|
+
endedAt: row.ended_at ? toIso(row.ended_at) : null,
|
|
47
|
+
messageCount: Number(row.message_count || 0),
|
|
48
|
+
metadata: parseJsonObject(row.metadata_json),
|
|
49
|
+
createdAt: toIso(row.created_at),
|
|
50
|
+
updatedAt: toIso(row.updated_at)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeCursorPagination(pagination = {}, { defaultLimit = 20, maxLimit = 200 } = {}) {
|
|
55
|
+
const cursor = parsePositiveInteger(pagination.cursor) || 0;
|
|
56
|
+
const limit = Math.max(1, Math.min(maxLimit, parsePositiveInteger(pagination.limit) || defaultLimit));
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
cursor,
|
|
60
|
+
limit
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createConversationBaseQuery(client) {
|
|
65
|
+
return client(`${assistantRuntimeConfig.conversationsTable} as c`).select("c.*");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createRepository(knex) {
|
|
69
|
+
if (!knex || typeof knex !== "function") {
|
|
70
|
+
throw new Error("createConversationsRepository requires knex client.");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function findById(conversationId, options = {}) {
|
|
74
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
75
|
+
if (!numericConversationId) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const client = options?.trx || knex;
|
|
80
|
+
const row = await createConversationBaseQuery(client)
|
|
81
|
+
.where("c.id", numericConversationId)
|
|
82
|
+
.first();
|
|
83
|
+
|
|
84
|
+
return row ? mapConversationRow(row) : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function findByIdForActorScope(
|
|
88
|
+
conversationId,
|
|
89
|
+
{ workspaceId = null, actorUserId = null, surfaceId = "" } = {},
|
|
90
|
+
options = {}
|
|
91
|
+
) {
|
|
92
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
93
|
+
const numericActorUserId = parsePositiveInteger(actorUserId);
|
|
94
|
+
const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
|
|
95
|
+
if (!numericConversationId || !numericActorUserId || !normalizedSurfaceId) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const client = options?.trx || knex;
|
|
100
|
+
const query = createConversationBaseQuery(client)
|
|
101
|
+
.where("c.id", numericConversationId)
|
|
102
|
+
.where("c.created_by_user_id", numericActorUserId)
|
|
103
|
+
.where("c.surface_id", normalizedSurfaceId);
|
|
104
|
+
applyWorkspaceScope(query, "c.workspace_id", workspaceId);
|
|
105
|
+
const row = await query.first();
|
|
106
|
+
|
|
107
|
+
return row ? mapConversationRow(row) : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function create(payload = {}, options = {}) {
|
|
111
|
+
const client = options?.trx || knex;
|
|
112
|
+
const now = new Date();
|
|
113
|
+
const surfaceId = normalizeRequiredSurfaceId(payload.surfaceId);
|
|
114
|
+
const insertResult = await client(assistantRuntimeConfig.conversationsTable).insert({
|
|
115
|
+
workspace_id: normalizeWorkspaceId(payload.workspaceId),
|
|
116
|
+
created_by_user_id: parsePositiveInteger(payload.createdByUserId) || null,
|
|
117
|
+
title: normalizeText(payload.title) || "New conversation",
|
|
118
|
+
status: normalizeText(payload.status).toLowerCase() || "active",
|
|
119
|
+
provider: normalizeText(payload.provider),
|
|
120
|
+
model: normalizeText(payload.model),
|
|
121
|
+
surface_id: surfaceId,
|
|
122
|
+
message_count: parsePositiveInteger(payload.messageCount) || 0,
|
|
123
|
+
metadata_json: stringifyJsonObject(payload.metadata),
|
|
124
|
+
started_at: payload.startedAt ? new Date(payload.startedAt) : now,
|
|
125
|
+
ended_at: payload.endedAt ? new Date(payload.endedAt) : null,
|
|
126
|
+
created_at: now,
|
|
127
|
+
updated_at: now
|
|
128
|
+
});
|
|
129
|
+
const id = resolveInsertedId(insertResult);
|
|
130
|
+
if (!id) {
|
|
131
|
+
throw new Error("conversationsRepository.create could not resolve inserted id.");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return findById(id, {
|
|
135
|
+
trx: client
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function updateById(conversationId, patch = {}, options = {}) {
|
|
140
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
141
|
+
if (!numericConversationId) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const client = options?.trx || knex;
|
|
146
|
+
const updatePatch = {};
|
|
147
|
+
|
|
148
|
+
if (Object.hasOwn(patch, "title")) {
|
|
149
|
+
updatePatch.title = normalizeText(patch.title) || "New conversation";
|
|
150
|
+
}
|
|
151
|
+
if (Object.hasOwn(patch, "status")) {
|
|
152
|
+
updatePatch.status = normalizeText(patch.status).toLowerCase() || "active";
|
|
153
|
+
}
|
|
154
|
+
if (Object.hasOwn(patch, "provider")) {
|
|
155
|
+
updatePatch.provider = normalizeText(patch.provider);
|
|
156
|
+
}
|
|
157
|
+
if (Object.hasOwn(patch, "model")) {
|
|
158
|
+
updatePatch.model = normalizeText(patch.model);
|
|
159
|
+
}
|
|
160
|
+
if (Object.hasOwn(patch, "surfaceId")) {
|
|
161
|
+
updatePatch.surface_id = normalizeRequiredSurfaceId(patch.surfaceId);
|
|
162
|
+
}
|
|
163
|
+
if (Object.hasOwn(patch, "messageCount")) {
|
|
164
|
+
updatePatch.message_count = Math.max(0, Number(patch.messageCount || 0));
|
|
165
|
+
}
|
|
166
|
+
if (Object.hasOwn(patch, "endedAt")) {
|
|
167
|
+
updatePatch.ended_at = patch.endedAt ? new Date(patch.endedAt) : null;
|
|
168
|
+
}
|
|
169
|
+
if (Object.hasOwn(patch, "metadata")) {
|
|
170
|
+
updatePatch.metadata_json = stringifyJsonObject(patch.metadata);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (Object.keys(updatePatch).length > 0) {
|
|
174
|
+
updatePatch.updated_at = new Date();
|
|
175
|
+
await client(assistantRuntimeConfig.conversationsTable)
|
|
176
|
+
.where({ id: numericConversationId })
|
|
177
|
+
.update(updatePatch);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return findById(numericConversationId, {
|
|
181
|
+
trx: client
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function incrementMessageCount(conversationId, delta = 1, options = {}) {
|
|
186
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
187
|
+
if (!numericConversationId) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const client = options?.trx || knex;
|
|
192
|
+
const incrementBy = Number.isInteger(Number(delta)) ? Number(delta) : 1;
|
|
193
|
+
await client(assistantRuntimeConfig.conversationsTable)
|
|
194
|
+
.where({ id: numericConversationId })
|
|
195
|
+
.update({
|
|
196
|
+
message_count: client.raw("GREATEST(0, message_count + ?)", [incrementBy]),
|
|
197
|
+
updated_at: new Date()
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return findById(numericConversationId, {
|
|
201
|
+
trx: client
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function listForActorScope(
|
|
206
|
+
{ workspaceId = null, actorUserId = null, surfaceId = "", pagination = {}, filters = {} } = {},
|
|
207
|
+
options = {}
|
|
208
|
+
) {
|
|
209
|
+
const numericActorUserId = parsePositiveInteger(actorUserId);
|
|
210
|
+
const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
|
|
211
|
+
if (!numericActorUserId || !normalizedSurfaceId) {
|
|
212
|
+
return {
|
|
213
|
+
items: [],
|
|
214
|
+
nextCursor: null
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const client = options?.trx || knex;
|
|
219
|
+
const { cursor, limit } = normalizeCursorPagination(pagination);
|
|
220
|
+
let query = createConversationBaseQuery(client)
|
|
221
|
+
.where("c.created_by_user_id", numericActorUserId)
|
|
222
|
+
.where("c.surface_id", normalizedSurfaceId);
|
|
223
|
+
query = applyWorkspaceScope(query, "c.workspace_id", workspaceId);
|
|
224
|
+
|
|
225
|
+
const normalizedStatus = normalizeText(filters.status).toLowerCase();
|
|
226
|
+
if (normalizedStatus) {
|
|
227
|
+
query = query.where("c.status", normalizedStatus);
|
|
228
|
+
}
|
|
229
|
+
if (cursor > 0) {
|
|
230
|
+
query = query.where("c.id", "<", cursor);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const rows = await query
|
|
234
|
+
.orderBy("c.id", "desc")
|
|
235
|
+
.limit(limit + 1);
|
|
236
|
+
|
|
237
|
+
const hasMore = rows.length > limit;
|
|
238
|
+
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
239
|
+
const items = pageRows.map(mapConversationRow);
|
|
240
|
+
const nextCursor = hasMore && pageRows.length > 0 ? String(pageRows[pageRows.length - 1].id) : null;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
items,
|
|
244
|
+
nextCursor
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function transaction(callback) {
|
|
249
|
+
return runInTransaction(knex, callback);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return Object.freeze({
|
|
253
|
+
findById,
|
|
254
|
+
findByIdForActorScope,
|
|
255
|
+
create,
|
|
256
|
+
updateById,
|
|
257
|
+
incrementMessageCount,
|
|
258
|
+
listForActorScope,
|
|
259
|
+
transaction
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export { createRepository };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
2
|
+
import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
3
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
import {
|
|
5
|
+
parseJsonObject,
|
|
6
|
+
resolveInsertedId,
|
|
7
|
+
stringifyJsonObject,
|
|
8
|
+
toIso
|
|
9
|
+
} from "@jskit-ai/assistant-core/server";
|
|
10
|
+
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
11
|
+
|
|
12
|
+
function normalizeWorkspaceId(value) {
|
|
13
|
+
return parsePositiveInteger(value) || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function applyWorkspaceScope(query, columnName, workspaceId) {
|
|
17
|
+
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
|
|
18
|
+
if (normalizedWorkspaceId) {
|
|
19
|
+
return query.where(columnName, normalizedWorkspaceId);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return query.whereNull(columnName);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mapMessageRow(row = {}) {
|
|
26
|
+
return {
|
|
27
|
+
id: Number(row.id),
|
|
28
|
+
conversationId: Number(row.conversation_id),
|
|
29
|
+
workspaceId: normalizeWorkspaceId(row.workspace_id),
|
|
30
|
+
seq: Number(row.seq),
|
|
31
|
+
role: String(row.role || ""),
|
|
32
|
+
kind: String(row.kind || "chat"),
|
|
33
|
+
clientMessageSid: String(row.client_message_sid || ""),
|
|
34
|
+
actorUserId: row.actor_user_id == null ? null : Number(row.actor_user_id),
|
|
35
|
+
contentText: row.content_text == null ? null : String(row.content_text),
|
|
36
|
+
metadata: parseJsonObject(row.metadata_json),
|
|
37
|
+
createdAt: toIso(row.created_at)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizePagination(pagination = {}, { defaultPage = 1, defaultPageSize = 200, maxPageSize = 500 } = {}) {
|
|
42
|
+
const page = Math.max(1, parsePositiveInteger(pagination.page) || defaultPage);
|
|
43
|
+
const pageSize = Math.max(1, Math.min(maxPageSize, parsePositiveInteger(pagination.pageSize) || defaultPageSize));
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
page,
|
|
47
|
+
pageSize
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function resolveNextSequence(client, conversationId) {
|
|
52
|
+
const row = await client(assistantRuntimeConfig.messagesTable)
|
|
53
|
+
.where({ conversation_id: conversationId })
|
|
54
|
+
.max({ maxSeq: "seq" })
|
|
55
|
+
.first();
|
|
56
|
+
const maxSeq = Number(row?.maxSeq || 0);
|
|
57
|
+
if (!Number.isInteger(maxSeq) || maxSeq < 0) {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return maxSeq + 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createRepository(knex) {
|
|
65
|
+
if (!knex || typeof knex !== "function") {
|
|
66
|
+
throw new Error("createMessagesRepository requires knex client.");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function findById(messageId, options = {}) {
|
|
70
|
+
const numericMessageId = parsePositiveInteger(messageId);
|
|
71
|
+
if (!numericMessageId) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const client = options?.trx || knex;
|
|
76
|
+
const row = await client(assistantRuntimeConfig.messagesTable)
|
|
77
|
+
.where({ id: numericMessageId })
|
|
78
|
+
.first();
|
|
79
|
+
|
|
80
|
+
return row ? mapMessageRow(row) : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function create(payload = {}, options = {}) {
|
|
84
|
+
const client = options?.trx || knex;
|
|
85
|
+
const conversationId = parsePositiveInteger(payload.conversationId);
|
|
86
|
+
if (!conversationId) {
|
|
87
|
+
throw new TypeError("messagesRepository.create requires conversationId.");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const seq = parsePositiveInteger(payload.seq) || (await resolveNextSequence(client, conversationId));
|
|
91
|
+
const insertResult = await client(assistantRuntimeConfig.messagesTable).insert({
|
|
92
|
+
conversation_id: conversationId,
|
|
93
|
+
workspace_id: normalizeWorkspaceId(payload.workspaceId),
|
|
94
|
+
seq,
|
|
95
|
+
role: normalizeText(payload.role).toLowerCase(),
|
|
96
|
+
kind: normalizeText(payload.kind).toLowerCase() || "chat",
|
|
97
|
+
client_message_sid: normalizeText(payload.clientMessageSid),
|
|
98
|
+
actor_user_id: parsePositiveInteger(payload.actorUserId) || null,
|
|
99
|
+
content_text: payload.contentText == null ? null : String(payload.contentText),
|
|
100
|
+
metadata_json: stringifyJsonObject(payload.metadata),
|
|
101
|
+
created_at: payload.createdAt ? new Date(payload.createdAt) : new Date()
|
|
102
|
+
});
|
|
103
|
+
const id = resolveInsertedId(insertResult);
|
|
104
|
+
if (!id) {
|
|
105
|
+
throw new Error("messagesRepository.create could not resolve inserted id.");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return findById(id, {
|
|
109
|
+
trx: client
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function countByConversationScope(conversationId, { workspaceId = null } = {}, options = {}) {
|
|
114
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
115
|
+
if (!numericConversationId) {
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const client = options?.trx || knex;
|
|
120
|
+
const query = client(assistantRuntimeConfig.messagesTable).where({
|
|
121
|
+
conversation_id: numericConversationId
|
|
122
|
+
});
|
|
123
|
+
applyWorkspaceScope(query, "workspace_id", workspaceId);
|
|
124
|
+
const row = await query.count({ total: "*" }).first();
|
|
125
|
+
|
|
126
|
+
const total = Number(row?.total || 0);
|
|
127
|
+
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function listByConversationScope(conversationId, { workspaceId = null } = {}, pagination = {}, options = {}) {
|
|
131
|
+
const numericConversationId = parsePositiveInteger(conversationId);
|
|
132
|
+
if (!numericConversationId) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const client = options?.trx || knex;
|
|
137
|
+
const { page, pageSize } = normalizePagination(pagination);
|
|
138
|
+
const offset = (page - 1) * pageSize;
|
|
139
|
+
|
|
140
|
+
const query = client(assistantRuntimeConfig.messagesTable).where({
|
|
141
|
+
conversation_id: numericConversationId
|
|
142
|
+
});
|
|
143
|
+
applyWorkspaceScope(query, "workspace_id", workspaceId);
|
|
144
|
+
const rows = await query
|
|
145
|
+
.orderBy("seq", "asc")
|
|
146
|
+
.orderBy("id", "asc")
|
|
147
|
+
.limit(pageSize)
|
|
148
|
+
.offset(offset);
|
|
149
|
+
|
|
150
|
+
return rows.map(mapMessageRow);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function transaction(callback) {
|
|
154
|
+
return runInTransaction(knex, callback);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return Object.freeze({
|
|
158
|
+
findById,
|
|
159
|
+
create,
|
|
160
|
+
countByConversationScope,
|
|
161
|
+
listByConversationScope,
|
|
162
|
+
transaction
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export { createRepository };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { AppError, parsePositiveInteger, requireAuth } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import { resolveWorkspace } from "@jskit-ai/users-core/server/support/resolveWorkspace";
|
|
4
|
+
import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
|
|
5
|
+
|
|
6
|
+
function createService({ assistantConfigRepository, consoleService = null, appConfig = {}, resolveAppConfig = null } = {}) {
|
|
7
|
+
if (!assistantConfigRepository || typeof assistantConfigRepository.findByScope !== "function") {
|
|
8
|
+
throw new Error("assistantConfigService requires assistantConfigRepository.findByScope().");
|
|
9
|
+
}
|
|
10
|
+
if (typeof assistantConfigRepository.upsertByScope !== "function") {
|
|
11
|
+
throw new Error("assistantConfigService requires assistantConfigRepository.upsertByScope().");
|
|
12
|
+
}
|
|
13
|
+
if (typeof assistantConfigRepository.createDefaultRecord !== "function") {
|
|
14
|
+
throw new Error("assistantConfigService requires assistantConfigRepository.createDefaultRecord().");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const resolveCurrentAppConfig =
|
|
18
|
+
typeof resolveAppConfig === "function" ? resolveAppConfig : () => appConfig;
|
|
19
|
+
|
|
20
|
+
function requireAssistantSurface(targetSurfaceId = "") {
|
|
21
|
+
const assistantSurface = resolveAssistantSurfaceConfig(resolveCurrentAppConfig(), targetSurfaceId);
|
|
22
|
+
if (assistantSurface) {
|
|
23
|
+
return assistantSurface;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
throw new AppError(404, "Assistant not found.");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function requireSettingsAccess(assistantSurface, context = {}, { mode = "read" } = {}) {
|
|
30
|
+
requireAuth({ context }, {
|
|
31
|
+
require: "authenticated"
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (assistantSurface?.settingsSurfaceRequiresWorkspace === true) {
|
|
35
|
+
requireAuth(
|
|
36
|
+
{ context },
|
|
37
|
+
mode === "write"
|
|
38
|
+
? {
|
|
39
|
+
require: "all",
|
|
40
|
+
permissions: ["workspace.settings.update"]
|
|
41
|
+
}
|
|
42
|
+
: {
|
|
43
|
+
require: "any",
|
|
44
|
+
permissions: ["workspace.settings.view", "workspace.settings.update"]
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (assistantSurface?.settingsSurfaceRequiresConsoleOwner !== true) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!consoleService || typeof consoleService.requireConsoleOwner !== "function") {
|
|
55
|
+
throw new Error("assistantConfigService requires consoleService.requireConsoleOwner() for console-owner settings surfaces.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await consoleService.requireConsoleOwner(context);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveConfigWorkspaceId(assistantSurface, workspace = null, input = {}, context = {}) {
|
|
62
|
+
if (assistantSurface?.configScope !== "workspace") {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const resolvedWorkspace = workspace || resolveWorkspace(context, input);
|
|
67
|
+
const workspaceId = parsePositiveInteger(resolvedWorkspace?.id);
|
|
68
|
+
if (!workspaceId) {
|
|
69
|
+
throw new AppError(409, "Workspace selection required.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return workspaceId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function getSettings(input = {}, options = {}) {
|
|
76
|
+
const assistantSurface = requireAssistantSurface(input?.targetSurfaceId);
|
|
77
|
+
await requireSettingsAccess(assistantSurface, options?.context, {
|
|
78
|
+
mode: "read"
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const workspaceId = resolveConfigWorkspaceId(assistantSurface, null, input, options?.context);
|
|
82
|
+
const existing = await assistantConfigRepository.findByScope({
|
|
83
|
+
targetSurfaceId: assistantSurface.targetSurfaceId,
|
|
84
|
+
workspaceId
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return existing || assistantConfigRepository.createDefaultRecord({
|
|
88
|
+
targetSurfaceId: assistantSurface.targetSurfaceId,
|
|
89
|
+
workspaceId
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function updateSettings(input = {}, patch = {}, options = {}) {
|
|
94
|
+
const assistantSurface = requireAssistantSurface(input?.targetSurfaceId);
|
|
95
|
+
await requireSettingsAccess(assistantSurface, options?.context, {
|
|
96
|
+
mode: "write"
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const workspaceId = resolveConfigWorkspaceId(assistantSurface, null, input, options?.context);
|
|
100
|
+
const normalizedPatch = normalizeObject(patch);
|
|
101
|
+
|
|
102
|
+
return assistantConfigRepository.upsertByScope({
|
|
103
|
+
targetSurfaceId: assistantSurface.targetSurfaceId,
|
|
104
|
+
workspaceId,
|
|
105
|
+
patch: normalizedPatch
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function resolveSystemPrompt(assistantSurface, workspace = null, _options = {}, serviceOptions = {}) {
|
|
110
|
+
const resolvedAssistantSurface = requireAssistantSurface(assistantSurface?.targetSurfaceId);
|
|
111
|
+
const workspaceId = resolveConfigWorkspaceId(
|
|
112
|
+
resolvedAssistantSurface,
|
|
113
|
+
workspace,
|
|
114
|
+
serviceOptions?.input || {},
|
|
115
|
+
serviceOptions?.context
|
|
116
|
+
);
|
|
117
|
+
const existing = await assistantConfigRepository.findByScope({
|
|
118
|
+
targetSurfaceId: resolvedAssistantSurface.targetSurfaceId,
|
|
119
|
+
workspaceId
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return String(existing?.settings?.systemPrompt || "");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return Object.freeze({
|
|
126
|
+
getSettings,
|
|
127
|
+
updateSettings,
|
|
128
|
+
resolveSystemPrompt
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { createService };
|