@jskit-ai/assistant-runtime 0.1.3 → 0.1.4
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 +8 -8
- package/package.json +8 -8
- package/src/client/components/AssistantSettingsClientElement.vue +3 -3
- package/src/client/composables/useAssistantRuntime.js +29 -29
- package/src/server/repositories/assistantConfigRepository.js +10 -4
- package/src/server/repositories/conversationsRepository.js +46 -32
- package/src/server/repositories/messagesRepository.js +36 -22
- package/src/server/services/assistantConfigService.js +3 -3
- package/src/server/services/chatService.js +3 -4
- package/src/server/services/transcriptService.js +35 -37
- package/templates/migrations/assistant_config_initial.cjs +2 -2
- package/templates/migrations/assistant_transcripts_initial.cjs +7 -7
- package/test/repositoryContracts.test.js +32 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/assistant-runtime",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.4",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Shared assistant runtime with per-surface assistant registration.",
|
|
7
7
|
dependsOn: [
|
|
@@ -74,13 +74,13 @@ export default Object.freeze({
|
|
|
74
74
|
mutations: {
|
|
75
75
|
dependencies: {
|
|
76
76
|
runtime: {
|
|
77
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
78
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
79
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
80
|
-
"@jskit-ai/kernel": "0.1.
|
|
81
|
-
"@jskit-ai/shell-web": "0.1.
|
|
82
|
-
"@jskit-ai/users-core": "0.1.
|
|
83
|
-
"@jskit-ai/users-web": "0.1.
|
|
77
|
+
"@jskit-ai/assistant-core": "0.1.9",
|
|
78
|
+
"@jskit-ai/database-runtime": "0.1.33",
|
|
79
|
+
"@jskit-ai/http-runtime": "0.1.32",
|
|
80
|
+
"@jskit-ai/kernel": "0.1.33",
|
|
81
|
+
"@jskit-ai/shell-web": "0.1.32",
|
|
82
|
+
"@jskit-ai/users-core": "0.1.43",
|
|
83
|
+
"@jskit-ai/users-web": "0.1.48",
|
|
84
84
|
"@tanstack/vue-query": "^5.90.5",
|
|
85
85
|
"vuetify": "^4.0.0"
|
|
86
86
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./client": "./src/client/index.js",
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"./server/actionIds": "./src/server/actionIds.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
12
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
13
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
14
|
-
"@jskit-ai/kernel": "0.1.
|
|
15
|
-
"@jskit-ai/shell-web": "0.1.
|
|
16
|
-
"@jskit-ai/users-core": "0.1.
|
|
17
|
-
"@jskit-ai/users-web": "0.1.
|
|
11
|
+
"@jskit-ai/assistant-core": "0.1.9",
|
|
12
|
+
"@jskit-ai/database-runtime": "0.1.33",
|
|
13
|
+
"@jskit-ai/http-runtime": "0.1.32",
|
|
14
|
+
"@jskit-ai/kernel": "0.1.33",
|
|
15
|
+
"@jskit-ai/shell-web": "0.1.32",
|
|
16
|
+
"@jskit-ai/users-core": "0.1.43",
|
|
17
|
+
"@jskit-ai/users-web": "0.1.48",
|
|
18
18
|
"@tanstack/vue-query": "^5.90.5",
|
|
19
19
|
"vuetify": "^4.0.0"
|
|
20
20
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { computed, reactive, watch } from "vue";
|
|
26
26
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
|
|
27
27
|
import { getClientAppConfig } from "@jskit-ai/kernel/client";
|
|
28
|
-
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
28
|
+
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
29
29
|
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
30
30
|
import { assistantHttpClient, createAssistantApi, AssistantSettingsFormCard } from "@jskit-ai/assistant-core/client";
|
|
31
31
|
import { assistantConfigResource, assistantSettingsQueryKey, buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
|
|
@@ -65,8 +65,8 @@ const scope = computed(() => {
|
|
|
65
65
|
targetSurfaceId: normalizeText(assistantSurface.value?.targetSurfaceId).toLowerCase(),
|
|
66
66
|
workspaceSlug,
|
|
67
67
|
workspaceId: settingsRequiresWorkspace
|
|
68
|
-
?
|
|
69
|
-
:
|
|
68
|
+
? normalizeRecordId(placementSnapshot.value?.workspace?.id, { fallback: null })
|
|
69
|
+
: null
|
|
70
70
|
};
|
|
71
71
|
});
|
|
72
72
|
const hasScope = computed(() =>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed, ref, watch } from "vue";
|
|
2
2
|
import { useQueryClient } from "@tanstack/vue-query";
|
|
3
3
|
import { getClientAppConfig } from "@jskit-ai/kernel/client";
|
|
4
|
-
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
5
|
import { buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
|
|
6
6
|
import {
|
|
7
7
|
ASSISTANT_STREAM_EVENT_TYPES,
|
|
@@ -47,13 +47,13 @@ function buildScopeStorageKey(scope = {}) {
|
|
|
47
47
|
|
|
48
48
|
function readStoredActiveConversationId(scope = {}) {
|
|
49
49
|
if (typeof window === "undefined" || !window.sessionStorage) {
|
|
50
|
-
return
|
|
50
|
+
return "";
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
try {
|
|
54
|
-
return
|
|
54
|
+
return normalizeRecordId(window.sessionStorage.getItem(buildScopeStorageKey(scope)), { fallback: "" });
|
|
55
55
|
} catch {
|
|
56
|
-
return
|
|
56
|
+
return "";
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -62,11 +62,11 @@ function writeStoredActiveConversationId(scope = {}, conversationId) {
|
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const normalizedConversationId =
|
|
65
|
+
const normalizedConversationId = normalizeRecordId(conversationId, { fallback: null });
|
|
66
66
|
const storageKey = buildScopeStorageKey(scope);
|
|
67
67
|
try {
|
|
68
|
-
if (normalizedConversationId
|
|
69
|
-
window.sessionStorage.setItem(storageKey,
|
|
68
|
+
if (normalizedConversationId) {
|
|
69
|
+
window.sessionStorage.setItem(storageKey, normalizedConversationId);
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -164,7 +164,8 @@ function mapTranscriptEntriesToAssistantState(entries) {
|
|
|
164
164
|
const role = normalizeText(entry?.role).toLowerCase();
|
|
165
165
|
const kind = normalizeText(entry?.kind).toLowerCase();
|
|
166
166
|
const metadata = normalizeObject(entry?.metadata);
|
|
167
|
-
const
|
|
167
|
+
const transcriptId = normalizeRecordId(entry?.id, { fallback: null });
|
|
168
|
+
const messageId = transcriptId ? `transcript_${transcriptId}` : buildId("transcript");
|
|
168
169
|
|
|
169
170
|
if (kind === "chat" && (role === "user" || role === "assistant")) {
|
|
170
171
|
messages.push({
|
|
@@ -258,8 +259,8 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
258
259
|
targetSurfaceId: normalizeText(assistantSurface.value?.targetSurfaceId).toLowerCase(),
|
|
259
260
|
workspaceSlug,
|
|
260
261
|
workspaceId: assistantSurface.value?.runtimeSurfaceRequiresWorkspace
|
|
261
|
-
?
|
|
262
|
-
:
|
|
262
|
+
? normalizeRecordId(placementSnapshot.value?.workspace?.id, { fallback: null })
|
|
263
|
+
: null
|
|
263
264
|
};
|
|
264
265
|
});
|
|
265
266
|
const hasRuntimeScope = computed(() =>
|
|
@@ -278,7 +279,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
278
279
|
resolveSurfaceId: () => normalizeText(currentSurfaceId.value).toLowerCase()
|
|
279
280
|
});
|
|
280
281
|
|
|
281
|
-
const activeConversationId = computed(() =>
|
|
282
|
+
const activeConversationId = computed(() => normalizeRecordId(conversationId.value, { fallback: "" }));
|
|
282
283
|
const isAdminSurface = computed(() => normalizeText(currentSurfaceId.value).toLowerCase() === "admin");
|
|
283
284
|
const canSend = computed(() => {
|
|
284
285
|
return Boolean(
|
|
@@ -320,8 +321,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
320
321
|
}),
|
|
321
322
|
initialPageParam: null,
|
|
322
323
|
dedupeBy(entry) {
|
|
323
|
-
|
|
324
|
-
return conversationNumericId > 0 ? String(conversationNumericId) : normalizeText(entry?.id);
|
|
324
|
+
return normalizeRecordId(entry?.id, { fallback: normalizeText(entry?.id) });
|
|
325
325
|
},
|
|
326
326
|
enabled: computed(() => hasRuntimeScope.value),
|
|
327
327
|
queryOptions: {
|
|
@@ -345,15 +345,15 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
345
345
|
return;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
const
|
|
349
|
-
if (
|
|
350
|
-
writeStoredActiveConversationId(runtimeScope.value,
|
|
348
|
+
const nextConversationIdKey = normalizeRecordId(nextConversationId, { fallback: null });
|
|
349
|
+
if (nextConversationIdKey) {
|
|
350
|
+
writeStoredActiveConversationId(runtimeScope.value, nextConversationIdKey);
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
const
|
|
355
|
-
if (
|
|
356
|
-
writeStoredActiveConversationId(runtimeScope.value,
|
|
354
|
+
const previousConversationIdKey = normalizeRecordId(previousConversationId, { fallback: null });
|
|
355
|
+
if (previousConversationIdKey) {
|
|
356
|
+
writeStoredActiveConversationId(runtimeScope.value, "");
|
|
357
357
|
}
|
|
358
358
|
});
|
|
359
359
|
|
|
@@ -378,8 +378,8 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
378
378
|
return;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
381
|
+
const activeConversationIdKey = normalizeRecordId(nextConversationId, { fallback: null });
|
|
382
|
+
if (activeConversationIdKey) {
|
|
383
383
|
return;
|
|
384
384
|
}
|
|
385
385
|
|
|
@@ -394,10 +394,10 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
const hasStoredConversation = sourceEntries.some(
|
|
397
|
-
(entry) =>
|
|
397
|
+
(entry) => normalizeRecordId(entry?.id, { fallback: null }) === storedConversationId
|
|
398
398
|
);
|
|
399
399
|
if (!hasStoredConversation) {
|
|
400
|
-
writeStoredActiveConversationId(nextRuntimeScope,
|
|
400
|
+
writeStoredActiveConversationId(nextRuntimeScope, "");
|
|
401
401
|
return;
|
|
402
402
|
}
|
|
403
403
|
|
|
@@ -458,13 +458,13 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
458
458
|
return;
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
const parsedConversationId =
|
|
461
|
+
const parsedConversationId = normalizeRecordId(normalizedConversationId, { fallback: null });
|
|
462
462
|
if (!parsedConversationId) {
|
|
463
463
|
return;
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
const previousConversationId = conversationId.value;
|
|
467
|
-
conversationId.value =
|
|
467
|
+
conversationId.value = parsedConversationId;
|
|
468
468
|
isRestoringConversation.value = true;
|
|
469
469
|
setRuntimeError("");
|
|
470
470
|
|
|
@@ -507,7 +507,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
507
507
|
input.value = "";
|
|
508
508
|
setRuntimeError("");
|
|
509
509
|
conversationId.value = null;
|
|
510
|
-
writeStoredActiveConversationId(runtimeScope.value,
|
|
510
|
+
writeStoredActiveConversationId(runtimeScope.value, "");
|
|
511
511
|
isStreaming.value = false;
|
|
512
512
|
isRestoringConversation.value = false;
|
|
513
513
|
abortController.value = null;
|
|
@@ -546,7 +546,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
546
546
|
const messageId = buildId("message");
|
|
547
547
|
const assistantMessageId = buildId("assistant");
|
|
548
548
|
const history = buildHistory(messages.value);
|
|
549
|
-
const parsedConversationId =
|
|
549
|
+
const parsedConversationId = normalizeRecordId(conversationId.value, { fallback: null });
|
|
550
550
|
|
|
551
551
|
appendMessage({
|
|
552
552
|
id: buildId("user"),
|
|
@@ -581,7 +581,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
581
581
|
await runtimeApi.streamChat(
|
|
582
582
|
{
|
|
583
583
|
messageId,
|
|
584
|
-
...(parsedConversationId
|
|
584
|
+
...(parsedConversationId ? { conversationId: parsedConversationId } : {}),
|
|
585
585
|
input: normalizedInput,
|
|
586
586
|
history
|
|
587
587
|
},
|
|
@@ -591,7 +591,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
591
591
|
const eventType = normalizeAssistantStreamEventType(event?.type, "");
|
|
592
592
|
|
|
593
593
|
if (eventType === ASSISTANT_STREAM_EVENT_TYPES.META && Object.hasOwn(event || {}, "conversationId")) {
|
|
594
|
-
conversationId.value = event?.conversationId
|
|
594
|
+
conversationId.value = normalizeRecordId(event?.conversationId, { fallback: null });
|
|
595
595
|
return;
|
|
596
596
|
}
|
|
597
597
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeDbRecordId, createWithTransaction } from "@jskit-ai/database-runtime/shared";
|
|
2
2
|
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
3
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
4
|
import { resolveInsertedId } from "@jskit-ai/assistant-core/server";
|
|
5
5
|
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
6
6
|
|
|
@@ -9,7 +9,11 @@ function normalizeTargetSurfaceId(value = "") {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function normalizeWorkspaceId(value) {
|
|
12
|
-
return
|
|
12
|
+
return normalizeRecordId(value, { fallback: null });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeWorkspaceDbId(value) {
|
|
16
|
+
return normalizeDbRecordId(value, { fallback: null });
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
function buildScopeKey(targetSurfaceId, workspaceId = null) {
|
|
@@ -29,7 +33,7 @@ function mapConfigRow(row = {}) {
|
|
|
29
33
|
return {
|
|
30
34
|
targetSurfaceId: normalizeTargetSurfaceId(row.target_surface_id),
|
|
31
35
|
scopeKey: normalizeText(row.scope_key),
|
|
32
|
-
workspaceId:
|
|
36
|
+
workspaceId: normalizeWorkspaceDbId(row.workspace_id),
|
|
33
37
|
settings: {
|
|
34
38
|
systemPrompt: String(row.system_prompt || "")
|
|
35
39
|
}
|
|
@@ -57,6 +61,7 @@ function createRepository(knex) {
|
|
|
57
61
|
if (!knex || typeof knex !== "function") {
|
|
58
62
|
throw new Error("createAssistantConfigRepository requires knex client.");
|
|
59
63
|
}
|
|
64
|
+
const withTransaction = createWithTransaction(knex);
|
|
60
65
|
|
|
61
66
|
async function findByScope({ targetSurfaceId = "", workspaceId = null } = {}, options = {}) {
|
|
62
67
|
const client = options?.trx || knex;
|
|
@@ -139,6 +144,7 @@ function createRepository(knex) {
|
|
|
139
144
|
}
|
|
140
145
|
|
|
141
146
|
return Object.freeze({
|
|
147
|
+
withTransaction,
|
|
142
148
|
createDefaultRecord,
|
|
143
149
|
findByScope,
|
|
144
150
|
upsertByScope
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
2
|
-
import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
1
|
+
import { createWithTransaction, normalizeDbRecordId, runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
3
2
|
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
4
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
4
|
import {
|
|
6
5
|
parseJsonObject,
|
|
7
6
|
resolveInsertedId,
|
|
@@ -11,7 +10,15 @@ import {
|
|
|
11
10
|
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
12
11
|
|
|
13
12
|
function normalizeWorkspaceId(value) {
|
|
14
|
-
return
|
|
13
|
+
return normalizeRecordId(value, { fallback: null });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeDbWorkspaceId(value) {
|
|
17
|
+
return normalizeDbRecordId(value, { fallback: null });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeInputRecordId(value) {
|
|
21
|
+
return normalizeRecordId(value, { fallback: null });
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
function normalizeRequiredSurfaceId(value) {
|
|
@@ -34,10 +41,10 @@ function applyWorkspaceScope(query, columnName, workspaceId) {
|
|
|
34
41
|
|
|
35
42
|
function mapConversationRow(row = {}) {
|
|
36
43
|
return {
|
|
37
|
-
id:
|
|
38
|
-
workspaceId:
|
|
44
|
+
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
45
|
+
workspaceId: normalizeDbWorkspaceId(row.workspace_id),
|
|
39
46
|
title: String(row.title || "New conversation"),
|
|
40
|
-
createdByUserId: row.created_by_user_id == null ? null :
|
|
47
|
+
createdByUserId: row.created_by_user_id == null ? null : normalizeDbRecordId(row.created_by_user_id, { fallback: null }),
|
|
41
48
|
status: String(row.status || "active"),
|
|
42
49
|
provider: String(row.provider || ""),
|
|
43
50
|
model: String(row.model || ""),
|
|
@@ -52,8 +59,11 @@ function mapConversationRow(row = {}) {
|
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
function normalizeCursorPagination(pagination = {}, { defaultLimit = 20, maxLimit = 200 } = {}) {
|
|
55
|
-
const cursor =
|
|
56
|
-
const
|
|
62
|
+
const cursor = normalizeInputRecordId(pagination.cursor) || "";
|
|
63
|
+
const parsedLimit = Number(pagination.limit);
|
|
64
|
+
const limit = Number.isInteger(parsedLimit) && parsedLimit > 0
|
|
65
|
+
? Math.max(1, Math.min(maxLimit, parsedLimit))
|
|
66
|
+
: defaultLimit;
|
|
57
67
|
|
|
58
68
|
return {
|
|
59
69
|
cursor,
|
|
@@ -69,16 +79,17 @@ function createRepository(knex) {
|
|
|
69
79
|
if (!knex || typeof knex !== "function") {
|
|
70
80
|
throw new Error("createConversationsRepository requires knex client.");
|
|
71
81
|
}
|
|
82
|
+
const withTransaction = createWithTransaction(knex);
|
|
72
83
|
|
|
73
84
|
async function findById(conversationId, options = {}) {
|
|
74
|
-
const
|
|
75
|
-
if (!
|
|
85
|
+
const normalizedConversationId = normalizeInputRecordId(conversationId);
|
|
86
|
+
if (!normalizedConversationId) {
|
|
76
87
|
return null;
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
const client = options?.trx || knex;
|
|
80
91
|
const row = await createConversationBaseQuery(client)
|
|
81
|
-
.where("c.id",
|
|
92
|
+
.where("c.id", normalizedConversationId)
|
|
82
93
|
.first();
|
|
83
94
|
|
|
84
95
|
return row ? mapConversationRow(row) : null;
|
|
@@ -89,17 +100,17 @@ function createRepository(knex) {
|
|
|
89
100
|
{ workspaceId = null, actorUserId = null, surfaceId = "" } = {},
|
|
90
101
|
options = {}
|
|
91
102
|
) {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
103
|
+
const normalizedConversationId = normalizeInputRecordId(conversationId);
|
|
104
|
+
const normalizedActorUserId = normalizeInputRecordId(actorUserId);
|
|
94
105
|
const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
|
|
95
|
-
if (!
|
|
106
|
+
if (!normalizedConversationId || !normalizedActorUserId || !normalizedSurfaceId) {
|
|
96
107
|
return null;
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
const client = options?.trx || knex;
|
|
100
111
|
const query = createConversationBaseQuery(client)
|
|
101
|
-
.where("c.id",
|
|
102
|
-
.where("c.created_by_user_id",
|
|
112
|
+
.where("c.id", normalizedConversationId)
|
|
113
|
+
.where("c.created_by_user_id", normalizedActorUserId)
|
|
103
114
|
.where("c.surface_id", normalizedSurfaceId);
|
|
104
115
|
applyWorkspaceScope(query, "c.workspace_id", workspaceId);
|
|
105
116
|
const row = await query.first();
|
|
@@ -113,13 +124,13 @@ function createRepository(knex) {
|
|
|
113
124
|
const surfaceId = normalizeRequiredSurfaceId(payload.surfaceId);
|
|
114
125
|
const insertResult = await client(assistantRuntimeConfig.conversationsTable).insert({
|
|
115
126
|
workspace_id: normalizeWorkspaceId(payload.workspaceId),
|
|
116
|
-
created_by_user_id:
|
|
127
|
+
created_by_user_id: normalizeInputRecordId(payload.createdByUserId),
|
|
117
128
|
title: normalizeText(payload.title) || "New conversation",
|
|
118
129
|
status: normalizeText(payload.status).toLowerCase() || "active",
|
|
119
130
|
provider: normalizeText(payload.provider),
|
|
120
131
|
model: normalizeText(payload.model),
|
|
121
132
|
surface_id: surfaceId,
|
|
122
|
-
message_count:
|
|
133
|
+
message_count: Math.max(0, Number(payload.messageCount || 0)),
|
|
123
134
|
metadata_json: stringifyJsonObject(payload.metadata),
|
|
124
135
|
started_at: payload.startedAt ? new Date(payload.startedAt) : now,
|
|
125
136
|
ended_at: payload.endedAt ? new Date(payload.endedAt) : null,
|
|
@@ -137,8 +148,8 @@ function createRepository(knex) {
|
|
|
137
148
|
}
|
|
138
149
|
|
|
139
150
|
async function updateById(conversationId, patch = {}, options = {}) {
|
|
140
|
-
const
|
|
141
|
-
if (!
|
|
151
|
+
const normalizedConversationId = normalizeInputRecordId(conversationId);
|
|
152
|
+
if (!normalizedConversationId) {
|
|
142
153
|
return null;
|
|
143
154
|
}
|
|
144
155
|
|
|
@@ -173,31 +184,31 @@ function createRepository(knex) {
|
|
|
173
184
|
if (Object.keys(updatePatch).length > 0) {
|
|
174
185
|
updatePatch.updated_at = new Date();
|
|
175
186
|
await client(assistantRuntimeConfig.conversationsTable)
|
|
176
|
-
.where({ id:
|
|
187
|
+
.where({ id: normalizedConversationId })
|
|
177
188
|
.update(updatePatch);
|
|
178
189
|
}
|
|
179
190
|
|
|
180
|
-
return findById(
|
|
191
|
+
return findById(normalizedConversationId, {
|
|
181
192
|
trx: client
|
|
182
193
|
});
|
|
183
194
|
}
|
|
184
195
|
|
|
185
196
|
async function incrementMessageCount(conversationId, delta = 1, options = {}) {
|
|
186
|
-
const
|
|
187
|
-
if (!
|
|
197
|
+
const normalizedConversationId = normalizeInputRecordId(conversationId);
|
|
198
|
+
if (!normalizedConversationId) {
|
|
188
199
|
return null;
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
const client = options?.trx || knex;
|
|
192
203
|
const incrementBy = Number.isInteger(Number(delta)) ? Number(delta) : 1;
|
|
193
204
|
await client(assistantRuntimeConfig.conversationsTable)
|
|
194
|
-
.where({ id:
|
|
205
|
+
.where({ id: normalizedConversationId })
|
|
195
206
|
.update({
|
|
196
207
|
message_count: client.raw("GREATEST(0, message_count + ?)", [incrementBy]),
|
|
197
208
|
updated_at: new Date()
|
|
198
209
|
});
|
|
199
210
|
|
|
200
|
-
return findById(
|
|
211
|
+
return findById(normalizedConversationId, {
|
|
201
212
|
trx: client
|
|
202
213
|
});
|
|
203
214
|
}
|
|
@@ -206,9 +217,9 @@ function createRepository(knex) {
|
|
|
206
217
|
{ workspaceId = null, actorUserId = null, surfaceId = "", pagination = {}, filters = {} } = {},
|
|
207
218
|
options = {}
|
|
208
219
|
) {
|
|
209
|
-
const
|
|
220
|
+
const normalizedActorUserId = normalizeInputRecordId(actorUserId);
|
|
210
221
|
const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
|
|
211
|
-
if (!
|
|
222
|
+
if (!normalizedActorUserId || !normalizedSurfaceId) {
|
|
212
223
|
return {
|
|
213
224
|
items: [],
|
|
214
225
|
nextCursor: null
|
|
@@ -218,7 +229,7 @@ function createRepository(knex) {
|
|
|
218
229
|
const client = options?.trx || knex;
|
|
219
230
|
const { cursor, limit } = normalizeCursorPagination(pagination);
|
|
220
231
|
let query = createConversationBaseQuery(client)
|
|
221
|
-
.where("c.created_by_user_id",
|
|
232
|
+
.where("c.created_by_user_id", normalizedActorUserId)
|
|
222
233
|
.where("c.surface_id", normalizedSurfaceId);
|
|
223
234
|
query = applyWorkspaceScope(query, "c.workspace_id", workspaceId);
|
|
224
235
|
|
|
@@ -226,7 +237,7 @@ function createRepository(knex) {
|
|
|
226
237
|
if (normalizedStatus) {
|
|
227
238
|
query = query.where("c.status", normalizedStatus);
|
|
228
239
|
}
|
|
229
|
-
if (cursor
|
|
240
|
+
if (cursor) {
|
|
230
241
|
query = query.where("c.id", "<", cursor);
|
|
231
242
|
}
|
|
232
243
|
|
|
@@ -237,7 +248,9 @@ function createRepository(knex) {
|
|
|
237
248
|
const hasMore = rows.length > limit;
|
|
238
249
|
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
239
250
|
const items = pageRows.map(mapConversationRow);
|
|
240
|
-
const nextCursor = hasMore && pageRows.length > 0
|
|
251
|
+
const nextCursor = hasMore && pageRows.length > 0
|
|
252
|
+
? normalizeDbRecordId(pageRows[pageRows.length - 1].id, { fallback: null })
|
|
253
|
+
: null;
|
|
241
254
|
|
|
242
255
|
return {
|
|
243
256
|
items,
|
|
@@ -250,6 +263,7 @@ function createRepository(knex) {
|
|
|
250
263
|
}
|
|
251
264
|
|
|
252
265
|
return Object.freeze({
|
|
266
|
+
withTransaction,
|
|
253
267
|
findById,
|
|
254
268
|
findByIdForActorScope,
|
|
255
269
|
create,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
2
|
-
import {
|
|
3
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { createWithTransaction, normalizeDbRecordId, runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
2
|
+
import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
3
|
import {
|
|
5
4
|
parseJsonObject,
|
|
6
5
|
resolveInsertedId,
|
|
@@ -10,7 +9,15 @@ import {
|
|
|
10
9
|
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
11
10
|
|
|
12
11
|
function normalizeWorkspaceId(value) {
|
|
13
|
-
return
|
|
12
|
+
return normalizeRecordId(value, { fallback: null });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeDbWorkspaceId(value) {
|
|
16
|
+
return normalizeDbRecordId(value, { fallback: null });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeInputRecordId(value) {
|
|
20
|
+
return normalizeRecordId(value, { fallback: null });
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
function applyWorkspaceScope(query, columnName, workspaceId) {
|
|
@@ -24,14 +31,14 @@ function applyWorkspaceScope(query, columnName, workspaceId) {
|
|
|
24
31
|
|
|
25
32
|
function mapMessageRow(row = {}) {
|
|
26
33
|
return {
|
|
27
|
-
id:
|
|
28
|
-
conversationId:
|
|
29
|
-
workspaceId:
|
|
34
|
+
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
35
|
+
conversationId: normalizeDbRecordId(row.conversation_id, { fallback: "" }),
|
|
36
|
+
workspaceId: normalizeDbWorkspaceId(row.workspace_id),
|
|
30
37
|
seq: Number(row.seq),
|
|
31
38
|
role: String(row.role || ""),
|
|
32
39
|
kind: String(row.kind || "chat"),
|
|
33
40
|
clientMessageSid: String(row.client_message_sid || ""),
|
|
34
|
-
actorUserId: row.actor_user_id == null ? null :
|
|
41
|
+
actorUserId: row.actor_user_id == null ? null : normalizeDbRecordId(row.actor_user_id, { fallback: null }),
|
|
35
42
|
contentText: row.content_text == null ? null : String(row.content_text),
|
|
36
43
|
metadata: parseJsonObject(row.metadata_json),
|
|
37
44
|
createdAt: toIso(row.created_at)
|
|
@@ -39,8 +46,12 @@ function mapMessageRow(row = {}) {
|
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
function normalizePagination(pagination = {}, { defaultPage = 1, defaultPageSize = 200, maxPageSize = 500 } = {}) {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
49
|
+
const rawPage = Number(pagination.page);
|
|
50
|
+
const rawPageSize = Number(pagination.pageSize);
|
|
51
|
+
const page = Number.isInteger(rawPage) && rawPage > 0 ? rawPage : defaultPage;
|
|
52
|
+
const pageSize = Number.isInteger(rawPageSize) && rawPageSize > 0
|
|
53
|
+
? Math.max(1, Math.min(maxPageSize, rawPageSize))
|
|
54
|
+
: defaultPageSize;
|
|
44
55
|
|
|
45
56
|
return {
|
|
46
57
|
page,
|
|
@@ -65,16 +76,17 @@ function createRepository(knex) {
|
|
|
65
76
|
if (!knex || typeof knex !== "function") {
|
|
66
77
|
throw new Error("createMessagesRepository requires knex client.");
|
|
67
78
|
}
|
|
79
|
+
const withTransaction = createWithTransaction(knex);
|
|
68
80
|
|
|
69
81
|
async function findById(messageId, options = {}) {
|
|
70
|
-
const
|
|
71
|
-
if (!
|
|
82
|
+
const normalizedMessageId = normalizeInputRecordId(messageId);
|
|
83
|
+
if (!normalizedMessageId) {
|
|
72
84
|
return null;
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
const client = options?.trx || knex;
|
|
76
88
|
const row = await client(assistantRuntimeConfig.messagesTable)
|
|
77
|
-
.where({ id:
|
|
89
|
+
.where({ id: normalizedMessageId })
|
|
78
90
|
.first();
|
|
79
91
|
|
|
80
92
|
return row ? mapMessageRow(row) : null;
|
|
@@ -82,12 +94,13 @@ function createRepository(knex) {
|
|
|
82
94
|
|
|
83
95
|
async function create(payload = {}, options = {}) {
|
|
84
96
|
const client = options?.trx || knex;
|
|
85
|
-
const conversationId =
|
|
97
|
+
const conversationId = normalizeInputRecordId(payload.conversationId);
|
|
86
98
|
if (!conversationId) {
|
|
87
99
|
throw new TypeError("messagesRepository.create requires conversationId.");
|
|
88
100
|
}
|
|
89
101
|
|
|
90
|
-
const
|
|
102
|
+
const providedSeq = Number(payload.seq);
|
|
103
|
+
const seq = Number.isInteger(providedSeq) && providedSeq > 0 ? providedSeq : (await resolveNextSequence(client, conversationId));
|
|
91
104
|
const insertResult = await client(assistantRuntimeConfig.messagesTable).insert({
|
|
92
105
|
conversation_id: conversationId,
|
|
93
106
|
workspace_id: normalizeWorkspaceId(payload.workspaceId),
|
|
@@ -95,7 +108,7 @@ function createRepository(knex) {
|
|
|
95
108
|
role: normalizeText(payload.role).toLowerCase(),
|
|
96
109
|
kind: normalizeText(payload.kind).toLowerCase() || "chat",
|
|
97
110
|
client_message_sid: normalizeText(payload.clientMessageSid),
|
|
98
|
-
actor_user_id:
|
|
111
|
+
actor_user_id: normalizeInputRecordId(payload.actorUserId),
|
|
99
112
|
content_text: payload.contentText == null ? null : String(payload.contentText),
|
|
100
113
|
metadata_json: stringifyJsonObject(payload.metadata),
|
|
101
114
|
created_at: payload.createdAt ? new Date(payload.createdAt) : new Date()
|
|
@@ -111,14 +124,14 @@ function createRepository(knex) {
|
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
async function countByConversationScope(conversationId, { workspaceId = null } = {}, options = {}) {
|
|
114
|
-
const
|
|
115
|
-
if (!
|
|
127
|
+
const normalizedConversationId = normalizeInputRecordId(conversationId);
|
|
128
|
+
if (!normalizedConversationId) {
|
|
116
129
|
return 0;
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
const client = options?.trx || knex;
|
|
120
133
|
const query = client(assistantRuntimeConfig.messagesTable).where({
|
|
121
|
-
conversation_id:
|
|
134
|
+
conversation_id: normalizedConversationId
|
|
122
135
|
});
|
|
123
136
|
applyWorkspaceScope(query, "workspace_id", workspaceId);
|
|
124
137
|
const row = await query.count({ total: "*" }).first();
|
|
@@ -128,8 +141,8 @@ function createRepository(knex) {
|
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
async function listByConversationScope(conversationId, { workspaceId = null } = {}, pagination = {}, options = {}) {
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
144
|
+
const normalizedConversationId = normalizeInputRecordId(conversationId);
|
|
145
|
+
if (!normalizedConversationId) {
|
|
133
146
|
return [];
|
|
134
147
|
}
|
|
135
148
|
|
|
@@ -138,7 +151,7 @@ function createRepository(knex) {
|
|
|
138
151
|
const offset = (page - 1) * pageSize;
|
|
139
152
|
|
|
140
153
|
const query = client(assistantRuntimeConfig.messagesTable).where({
|
|
141
|
-
conversation_id:
|
|
154
|
+
conversation_id: normalizedConversationId
|
|
142
155
|
});
|
|
143
156
|
applyWorkspaceScope(query, "workspace_id", workspaceId);
|
|
144
157
|
const rows = await query
|
|
@@ -155,6 +168,7 @@ function createRepository(knex) {
|
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
return Object.freeze({
|
|
171
|
+
withTransaction,
|
|
158
172
|
findById,
|
|
159
173
|
create,
|
|
160
174
|
countByConversationScope,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AppError,
|
|
2
|
-
import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { AppError, requireAuth } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { normalizeObject, normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
3
|
import { resolveWorkspace } from "@jskit-ai/users-core/server/support/resolveWorkspace";
|
|
4
4
|
import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
|
|
5
5
|
|
|
@@ -64,7 +64,7 @@ function createService({ assistantConfigRepository, consoleService = null, appCo
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const resolvedWorkspace = workspace || resolveWorkspace(context, input);
|
|
67
|
-
const workspaceId =
|
|
67
|
+
const workspaceId = normalizeRecordId(resolvedWorkspace?.id, { fallback: null });
|
|
68
68
|
if (!workspaceId) {
|
|
69
69
|
throw new AppError(409, "Workspace selection required.");
|
|
70
70
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AppError
|
|
2
|
-
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
|
+
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
3
|
import { resolveWorkspace } from "@jskit-ai/users-core/server/support/resolveWorkspace";
|
|
4
4
|
import { resolveWorkspaceSlug } from "@jskit-ai/assistant-core/server";
|
|
5
5
|
import {
|
|
@@ -12,8 +12,7 @@ const MAX_INPUT_CHARS = 8000;
|
|
|
12
12
|
const MAX_TOOL_ROUNDS = 4;
|
|
13
13
|
|
|
14
14
|
function normalizeConversationId(value) {
|
|
15
|
-
|
|
16
|
-
return parsed > 0 ? parsed : null;
|
|
15
|
+
return normalizeRecordId(value, { fallback: null });
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
function normalizeHistory(history = []) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppError, parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
2
2
|
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
3
|
-
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
4
|
import { normalizeConversationStatus } from "@jskit-ai/assistant-core/shared";
|
|
5
5
|
|
|
6
6
|
const DEFAULT_PAGE_SIZE = 20;
|
|
@@ -11,21 +11,21 @@ const DEFAULT_CONVERSATION_TITLE = "New conversation";
|
|
|
11
11
|
const MAX_CONVERSATION_TITLE_LENGTH = 80;
|
|
12
12
|
|
|
13
13
|
function resolveWorkspaceId(workspace, { required = false } = {}) {
|
|
14
|
-
const workspaceId =
|
|
14
|
+
const workspaceId = normalizeRecordId(workspace?.id || workspace, { fallback: null });
|
|
15
15
|
if (!workspaceId && required) {
|
|
16
16
|
throw new AppError(409, "Workspace selection required.");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
return workspaceId
|
|
19
|
+
return workspaceId;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function resolveActorUserId(user, { required = false } = {}) {
|
|
23
|
-
const actorUserId =
|
|
23
|
+
const actorUserId = normalizeRecordId(user?.id, { fallback: null });
|
|
24
24
|
if (!actorUserId && required) {
|
|
25
25
|
throw new AppError(401, "Authentication required.");
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
return actorUserId
|
|
28
|
+
return actorUserId;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function normalizePagination(pagination = {}, { defaultPageSize = DEFAULT_PAGE_SIZE, maxPageSize = MAX_PAGE_SIZE } = {}) {
|
|
@@ -39,7 +39,7 @@ function normalizePagination(pagination = {}, { defaultPageSize = DEFAULT_PAGE_S
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function normalizeCursorPagination(query = {}, { defaultLimit = DEFAULT_PAGE_SIZE, maxLimit = MAX_PAGE_SIZE } = {}) {
|
|
42
|
-
const cursor =
|
|
42
|
+
const cursor = normalizeRecordId(query.cursor, { fallback: null });
|
|
43
43
|
const limit = Math.max(1, Math.min(maxLimit, parsePositiveInteger(query.limit) || defaultLimit));
|
|
44
44
|
|
|
45
45
|
return {
|
|
@@ -92,7 +92,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
92
92
|
required: true
|
|
93
93
|
});
|
|
94
94
|
const source = normalizeObject(options);
|
|
95
|
-
const conversationId =
|
|
95
|
+
const conversationId = normalizeRecordId(source.conversationId, { fallback: null });
|
|
96
96
|
|
|
97
97
|
if (conversationId) {
|
|
98
98
|
const existing = await conversationsRepository.findByIdForActorScope(conversationId, {
|
|
@@ -143,16 +143,16 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
143
143
|
|
|
144
144
|
async function appendMessage(assistantSurface, conversationId, payload = {}, options = {}) {
|
|
145
145
|
const resolvedAssistantSurface = requireAssistantSurface(assistantSurface);
|
|
146
|
-
const
|
|
147
|
-
if (!
|
|
146
|
+
const normalizedConversationId = normalizeRecordId(conversationId, { fallback: null });
|
|
147
|
+
if (!normalizedConversationId) {
|
|
148
148
|
throw new TypeError("appendMessage requires conversationId.");
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
const source = normalizeObject(payload);
|
|
152
152
|
const context = normalizeObject(options.context);
|
|
153
|
-
const actorUserId =
|
|
153
|
+
const actorUserId = normalizeRecordId(source.actorUserId, { fallback: null }) || resolveActorUserId(context.actor);
|
|
154
154
|
const workspaceId = resolveExpectedWorkspaceId(resolvedAssistantSurface, options.workspace || context.workspace);
|
|
155
|
-
const conversation = await conversationsRepository.findByIdForActorScope(
|
|
155
|
+
const conversation = await conversationsRepository.findByIdForActorScope(normalizedConversationId, {
|
|
156
156
|
workspaceId,
|
|
157
157
|
actorUserId,
|
|
158
158
|
surfaceId: resolvedAssistantSurface.targetSurfaceId
|
|
@@ -162,7 +162,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
const createdMessage = await messagesRepository.create({
|
|
165
|
-
conversationId:
|
|
165
|
+
conversationId: normalizedConversationId,
|
|
166
166
|
workspaceId: conversation.workspaceId,
|
|
167
167
|
role: normalizeText(source.role).toLowerCase(),
|
|
168
168
|
kind: normalizeText(source.kind).toLowerCase() || "chat",
|
|
@@ -172,29 +172,29 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
172
172
|
metadata: normalizeObject(source.metadata)
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
await conversationsRepository.incrementMessageCount(
|
|
175
|
+
await conversationsRepository.incrementMessageCount(normalizedConversationId, 1);
|
|
176
176
|
|
|
177
177
|
const messageRole = normalizeText(source.role).toLowerCase();
|
|
178
178
|
const messageKind = normalizeText(source.kind).toLowerCase() || "chat";
|
|
179
179
|
if (messageRole === "user" && messageKind === "chat" && isDefaultConversationTitle(conversation.title)) {
|
|
180
180
|
const derivedTitle = deriveConversationTitleFromMessage(source.contentText);
|
|
181
181
|
if (derivedTitle) {
|
|
182
|
-
await conversationsRepository.updateById(
|
|
182
|
+
await conversationsRepository.updateById(normalizedConversationId, {
|
|
183
183
|
title: derivedTitle
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
return {
|
|
189
|
-
conversationId:
|
|
189
|
+
conversationId: normalizedConversationId,
|
|
190
190
|
message: createdMessage
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
async function completeConversation(assistantSurface, conversationId, payload = {}, options = {}) {
|
|
195
195
|
const resolvedAssistantSurface = requireAssistantSurface(assistantSurface);
|
|
196
|
-
const
|
|
197
|
-
if (!
|
|
196
|
+
const normalizedConversationId = normalizeRecordId(conversationId, { fallback: null });
|
|
197
|
+
if (!normalizedConversationId) {
|
|
198
198
|
throw new TypeError("completeConversation requires conversationId.");
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -204,7 +204,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
204
204
|
required: true
|
|
205
205
|
});
|
|
206
206
|
const workspaceId = resolveExpectedWorkspaceId(resolvedAssistantSurface, options.workspace || context.workspace);
|
|
207
|
-
const existing = await conversationsRepository.findByIdForActorScope(
|
|
207
|
+
const existing = await conversationsRepository.findByIdForActorScope(normalizedConversationId, {
|
|
208
208
|
workspaceId,
|
|
209
209
|
actorUserId,
|
|
210
210
|
surfaceId: resolvedAssistantSurface.targetSurfaceId
|
|
@@ -213,7 +213,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
213
213
|
throw new AppError(404, "Conversation not found.");
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
return conversationsRepository.updateById(
|
|
216
|
+
return conversationsRepository.updateById(normalizedConversationId, {
|
|
217
217
|
status: normalizeConversationStatus(source.status, {
|
|
218
218
|
fallback: "completed"
|
|
219
219
|
}),
|
|
@@ -243,18 +243,16 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
243
243
|
...(status ? { status } : {})
|
|
244
244
|
};
|
|
245
245
|
|
|
246
|
-
return conversationsRepository.listForActorScope(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
);
|
|
246
|
+
return conversationsRepository.listForActorScope({
|
|
247
|
+
workspaceId,
|
|
248
|
+
actorUserId,
|
|
249
|
+
surfaceId: resolvedAssistantSurface.targetSurfaceId,
|
|
250
|
+
pagination: {
|
|
251
|
+
cursor: pagination.cursor,
|
|
252
|
+
limit: pagination.limit
|
|
253
|
+
},
|
|
254
|
+
filters
|
|
255
|
+
});
|
|
258
256
|
}
|
|
259
257
|
|
|
260
258
|
async function getConversationMessagesForUser(assistantSurface, workspace, user, conversationId, query = {}) {
|
|
@@ -263,19 +261,19 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
263
261
|
const actorUserId = resolveActorUserId(user, {
|
|
264
262
|
required: true
|
|
265
263
|
});
|
|
266
|
-
const
|
|
267
|
-
if (!
|
|
264
|
+
const normalizedConversationId = normalizeRecordId(conversationId, { fallback: null });
|
|
265
|
+
if (!normalizedConversationId) {
|
|
268
266
|
throw new AppError(400, "Validation failed.", {
|
|
269
267
|
details: {
|
|
270
268
|
fieldErrors: {
|
|
271
|
-
conversationId: "conversationId must be a
|
|
269
|
+
conversationId: "conversationId must be a valid record id."
|
|
272
270
|
}
|
|
273
271
|
}
|
|
274
272
|
});
|
|
275
273
|
}
|
|
276
274
|
|
|
277
275
|
const conversation = await conversationsRepository.findByIdForActorScope(
|
|
278
|
-
|
|
276
|
+
normalizedConversationId,
|
|
279
277
|
{
|
|
280
278
|
workspaceId,
|
|
281
279
|
actorUserId,
|
|
@@ -291,7 +289,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
291
289
|
maxPageSize: MAX_MESSAGES_PAGE_SIZE
|
|
292
290
|
});
|
|
293
291
|
const total = await messagesRepository.countByConversationScope(
|
|
294
|
-
|
|
292
|
+
normalizedConversationId,
|
|
295
293
|
{
|
|
296
294
|
workspaceId
|
|
297
295
|
}
|
|
@@ -299,7 +297,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
299
297
|
const totalPages = Math.max(1, Math.ceil(total / pagination.pageSize));
|
|
300
298
|
const page = Math.min(pagination.page, totalPages);
|
|
301
299
|
const entries = await messagesRepository.listByConversationScope(
|
|
302
|
-
|
|
300
|
+
normalizedConversationId,
|
|
303
301
|
{
|
|
304
302
|
workspaceId
|
|
305
303
|
},
|
|
@@ -4,10 +4,10 @@ exports.up = async function up(knex) {
|
|
|
4
4
|
const hasWorkspacesTable = await knex.schema.hasTable("workspaces");
|
|
5
5
|
|
|
6
6
|
await knex.schema.createTable("assistant_config", (table) => {
|
|
7
|
-
table.
|
|
7
|
+
table.bigIncrements("id").primary();
|
|
8
8
|
table.string("target_surface_id", 64).notNullable();
|
|
9
9
|
table.string("scope_key", 160).notNullable();
|
|
10
|
-
table.
|
|
10
|
+
table.bigInteger("workspace_id").unsigned().nullable();
|
|
11
11
|
if (hasWorkspacesTable) {
|
|
12
12
|
table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
|
|
13
13
|
}
|
|
@@ -3,12 +3,12 @@ exports.up = async function up(knex) {
|
|
|
3
3
|
const hasConversationsTable = await knex.schema.hasTable("assistant_conversations");
|
|
4
4
|
if (!hasConversationsTable) {
|
|
5
5
|
await knex.schema.createTable("assistant_conversations", (table) => {
|
|
6
|
-
table.
|
|
7
|
-
table.
|
|
6
|
+
table.bigIncrements("id").primary();
|
|
7
|
+
table.bigInteger("workspace_id").unsigned().nullable();
|
|
8
8
|
if (hasWorkspacesTable) {
|
|
9
9
|
table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
|
|
10
10
|
}
|
|
11
|
-
table.
|
|
11
|
+
table.bigInteger("created_by_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL").index();
|
|
12
12
|
table.string("title", 160).notNullable().defaultTo("New conversation");
|
|
13
13
|
table.string("status", 32).notNullable().defaultTo("active");
|
|
14
14
|
table.string("provider", 64).notNullable().defaultTo("");
|
|
@@ -30,9 +30,9 @@ exports.up = async function up(knex) {
|
|
|
30
30
|
const hasMessagesTable = await knex.schema.hasTable("assistant_messages");
|
|
31
31
|
if (!hasMessagesTable) {
|
|
32
32
|
await knex.schema.createTable("assistant_messages", (table) => {
|
|
33
|
-
table.
|
|
34
|
-
table.
|
|
35
|
-
table.
|
|
33
|
+
table.bigIncrements("id").primary();
|
|
34
|
+
table.bigInteger("conversation_id").unsigned().notNullable().references("id").inTable("assistant_conversations").onDelete("CASCADE");
|
|
35
|
+
table.bigInteger("workspace_id").unsigned().nullable();
|
|
36
36
|
if (hasWorkspacesTable) {
|
|
37
37
|
table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
|
|
38
38
|
}
|
|
@@ -40,7 +40,7 @@ exports.up = async function up(knex) {
|
|
|
40
40
|
table.string("role", 32).notNullable();
|
|
41
41
|
table.string("kind", 32).notNullable().defaultTo("chat");
|
|
42
42
|
table.string("client_message_sid", 128).notNullable().defaultTo("");
|
|
43
|
-
table.
|
|
43
|
+
table.bigInteger("actor_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL").index();
|
|
44
44
|
table.text("content_text").nullable();
|
|
45
45
|
table.text("metadata_json").nullable();
|
|
46
46
|
table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createRepository as createAssistantConfigRepository } from "../src/server/repositories/assistantConfigRepository.js";
|
|
4
|
+
import { createRepository as createConversationsRepository } from "../src/server/repositories/conversationsRepository.js";
|
|
5
|
+
import { createRepository as createMessagesRepository } from "../src/server/repositories/messagesRepository.js";
|
|
6
|
+
|
|
7
|
+
function createKnexStub() {
|
|
8
|
+
const knex = Object.assign(() => {
|
|
9
|
+
throw new Error("query execution not expected");
|
|
10
|
+
}, {
|
|
11
|
+
async transaction(work) {
|
|
12
|
+
return work({ trxId: "trx-1" });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return knex;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test("assistant-runtime repositories expose withTransaction", async () => {
|
|
20
|
+
const knex = createKnexStub();
|
|
21
|
+
const repositories = [
|
|
22
|
+
createAssistantConfigRepository(knex),
|
|
23
|
+
createConversationsRepository(knex),
|
|
24
|
+
createMessagesRepository(knex)
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const repository of repositories) {
|
|
28
|
+
assert.equal(typeof repository.withTransaction, "function");
|
|
29
|
+
const result = await repository.withTransaction(async (trx) => ({ id: trx.trxId }));
|
|
30
|
+
assert.deepEqual(result, { id: "trx-1" });
|
|
31
|
+
}
|
|
32
|
+
});
|