@jskit-ai/assistant 0.1.31 → 0.1.34
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 +346 -142
- package/package.json +3 -19
- package/src/server/buildTemplateContext.js +107 -0
- package/templates/migrations/assistant_config_initial.cjs +25 -0
- package/templates/migrations/assistant_transcripts_initial.cjs +21 -14
- package/templates/src/local-package/client/components/AssistantSettingsClientElement.vue +88 -0
- package/templates/src/local-package/client/components/AssistantSurfaceClientElement.vue +10 -0
- package/{src/client/composables/useAssistantWorkspaceRuntime.js → templates/src/local-package/client/composables/useAssistantRuntime.js} +91 -114
- package/templates/src/local-package/client/index.js +3 -0
- package/templates/src/local-package/client/providers/AssistantClientProvider.js +16 -0
- package/templates/src/local-package/package.descriptor.mjs +85 -0
- package/templates/src/local-package/package.json +11 -0
- package/{src/server/AssistantServiceProvider.js → templates/src/local-package/server/AssistantProvider.js} +37 -61
- package/templates/src/local-package/server/actionIds.js +9 -0
- package/templates/src/local-package/server/actions.js +190 -0
- package/templates/src/local-package/server/registerRoutes.js +296 -0
- package/templates/src/local-package/server/repositories/assistantConfigRepository.js +141 -0
- package/{src → templates/src/local-package}/server/repositories/conversationsRepository.js +44 -45
- package/{src → templates/src/local-package}/server/repositories/messagesRepository.js +49 -34
- package/templates/src/local-package/server/services/assistantConfigService.js +90 -0
- package/{src → templates/src/local-package}/server/services/chatService.js +45 -37
- package/{src → templates/src/local-package}/server/services/transcriptService.js +61 -82
- package/templates/src/local-package/shared/assistantRuntimeConfig.js +13 -0
- package/templates/src/local-package/shared/index.js +1 -0
- package/templates/src/pages/assistant/index.vue +7 -0
- package/test/buildTemplateContext.test.js +112 -0
- package/test/packageDescriptor.test.js +69 -0
- package/src/client/components/AssistantClientElement.vue +0 -1316
- package/src/client/components/AssistantConsoleSettingsClientElement.vue +0 -70
- package/src/client/components/AssistantSettingsFormCard.vue +0 -76
- package/src/client/components/AssistantWorkspaceClientElement.vue +0 -15
- package/src/client/components/AssistantWorkspaceSettingsClientElement.vue +0 -72
- package/src/client/index.js +0 -10
- package/src/client/lib/assistantApi.js +0 -137
- package/src/client/lib/assistantHttpClient.js +0 -10
- package/src/client/lib/markdownRenderer.js +0 -31
- package/src/client/providers/AssistantWebClientProvider.js +0 -20
- package/src/server/actionIds.js +0 -11
- package/src/server/actions.js +0 -191
- package/src/server/lib/aiClient.js +0 -43
- package/src/server/lib/ndjson.js +0 -47
- package/src/server/lib/providers/anthropicClient.js +0 -375
- package/src/server/lib/providers/common.js +0 -150
- package/src/server/lib/providers/deepSeekClient.js +0 -22
- package/src/server/lib/providers/openAiClient.js +0 -13
- package/src/server/lib/providers/openAiCompatibleClient.js +0 -69
- package/src/server/lib/resolveWorkspaceSlug.js +0 -24
- package/src/server/lib/serviceToolCatalog.js +0 -459
- package/src/server/registerRoutes.js +0 -383
- package/src/server/repositories/assistantSettingsRepository.js +0 -100
- package/src/server/repositories/repositoryPersistenceUtils.js +0 -48
- package/src/server/services/assistantSettingsService.js +0 -149
- package/src/shared/assistantPaths.js +0 -50
- package/src/shared/assistantResource.js +0 -317
- package/src/shared/assistantSettingsResource.js +0 -197
- package/src/shared/index.js +0 -43
- package/src/shared/queryKeys.js +0 -69
- package/src/shared/settingsEvents.js +0 -6
- package/src/shared/streamEvents.js +0 -29
- package/src/shared/support/conversationStatus.js +0 -18
- package/src/shared/support/jsonObject.js +0 -18
- package/src/shared/support/positiveInteger.js +0 -9
- package/templates/migrations/assistant_settings_initial.cjs +0 -37
- package/templates/src/pages/admin/workspace/assistant/index.vue +0 -7
- package/test/aiConfigValidation.test.js +0 -15
- package/test/assistantApiSurfaceHeader.test.js +0 -64
- package/test/assistantResource.test.js +0 -53
- package/test/assistantSettingsResource.test.js +0 -48
- package/test/assistantSettingsService.test.js +0 -133
- package/test/chatService.test.js +0 -841
- package/test/descriptorSurfaceOption.test.js +0 -35
- package/test/queryKeys.test.js +0 -41
- package/test/resolveWorkspaceSlug.test.js +0 -83
- package/test/routeInputContracts.test.js +0 -286
- package/test/serviceToolCatalog.test.js +0 -1235
- package/test/transcriptService.test.js +0 -175
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { AppError, parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
2
2
|
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { resolveWorkspace } from "@jskit-ai/users-core/server/support/resolveWorkspace";
|
|
4
|
+
import { resolveWorkspaceSlug } from "@jskit-ai/assistant-core/server";
|
|
5
|
+
import {
|
|
6
|
+
ASSISTANT_STREAM_EVENT_TYPES
|
|
7
|
+
} from "@jskit-ai/assistant-core/shared";
|
|
8
|
+
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
6
9
|
|
|
7
10
|
const MAX_HISTORY_MESSAGES = 20;
|
|
8
11
|
const MAX_INPUT_CHARS = 8000;
|
|
@@ -86,12 +89,7 @@ function isAbortError(error) {
|
|
|
86
89
|
return false;
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
if (errorName === "AbortError") {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return false;
|
|
92
|
+
return String(error.name || "").trim() === "AbortError";
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
function extractTextDelta(deltaContent) {
|
|
@@ -148,11 +146,11 @@ function buildSystemPrompt({ toolDescriptors = [], workspaceSlug = "", customSys
|
|
|
148
146
|
const normalizedWorkspaceSlug = normalizeText(workspaceSlug).toLowerCase();
|
|
149
147
|
const workspaceLine = normalizedWorkspaceSlug
|
|
150
148
|
? `Current workspace slug: ${normalizedWorkspaceSlug}.`
|
|
151
|
-
: "
|
|
149
|
+
: "No workspace context is active for this assistant.";
|
|
152
150
|
const normalizedCustomSystemPrompt = String(customSystemPrompt || "").trim();
|
|
153
151
|
|
|
154
152
|
const promptSegments = [
|
|
155
|
-
|
|
153
|
+
`You are the assistant for the ${assistantRuntimeConfig.runtimeSurfaceId} surface.`,
|
|
156
154
|
"Use tools when they are necessary and only when available.",
|
|
157
155
|
"Do not mention tools that are not available.",
|
|
158
156
|
"When answering schema questions, rely only on tool contracts and tool results.",
|
|
@@ -534,9 +532,9 @@ function mergeAssistantMessageText(streamedText = "", completionText = "") {
|
|
|
534
532
|
return `${streamed}\n${completion}`;
|
|
535
533
|
}
|
|
536
534
|
|
|
537
|
-
function createChatService({ aiClient, transcriptService, serviceToolCatalog,
|
|
538
|
-
if (!aiClient || !transcriptService || !serviceToolCatalog || !
|
|
539
|
-
throw new Error("createChatService requires aiClient, transcriptService, serviceToolCatalog, and
|
|
535
|
+
function createChatService({ aiClient, transcriptService, serviceToolCatalog, assistantConfigService } = {}) {
|
|
536
|
+
if (!aiClient || !transcriptService || !serviceToolCatalog || !assistantConfigService) {
|
|
537
|
+
throw new Error("createChatService requires aiClient, transcriptService, serviceToolCatalog, and assistantConfigService.");
|
|
540
538
|
}
|
|
541
539
|
|
|
542
540
|
async function streamChat(payload = {}, options = {}) {
|
|
@@ -544,15 +542,15 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
544
542
|
throw new AppError(503, "Assistant provider is not configured.");
|
|
545
543
|
}
|
|
546
544
|
|
|
547
|
-
const source = normalizeStreamInput(payload);
|
|
548
545
|
const context = normalizeObject(options.context);
|
|
546
|
+
const workspace = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace ? resolveWorkspace(context, payload) : null;
|
|
547
|
+
const source = normalizeStreamInput(payload);
|
|
549
548
|
const streamWriter = options.streamWriter;
|
|
550
549
|
if (!hasStreamWriter(streamWriter)) {
|
|
551
550
|
throw new Error("assistant.chat.stream requires streamWriter methods.");
|
|
552
551
|
}
|
|
553
552
|
|
|
554
553
|
const actor = context.actor;
|
|
555
|
-
const workspace = context.workspace;
|
|
556
554
|
|
|
557
555
|
const conversationResult = await transcriptService.createConversationForTurn(
|
|
558
556
|
workspace,
|
|
@@ -561,7 +559,7 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
561
559
|
conversationId: source.conversationId,
|
|
562
560
|
provider: aiClient.provider,
|
|
563
561
|
model: aiClient.defaultModel,
|
|
564
|
-
|
|
562
|
+
surfaceId: assistantRuntimeConfig.runtimeSurfaceId,
|
|
565
563
|
messageId: source.messageId
|
|
566
564
|
},
|
|
567
565
|
{
|
|
@@ -583,27 +581,29 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
583
581
|
clientMessageSid: source.messageId,
|
|
584
582
|
contentText: source.input,
|
|
585
583
|
metadata: {
|
|
586
|
-
|
|
584
|
+
surfaceId: assistantRuntimeConfig.runtimeSurfaceId
|
|
587
585
|
}
|
|
588
586
|
},
|
|
589
587
|
{
|
|
590
|
-
context
|
|
588
|
+
context,
|
|
589
|
+
workspace
|
|
591
590
|
}
|
|
592
591
|
);
|
|
593
592
|
|
|
594
593
|
const toolSet = serviceToolCatalog.resolveToolSet(context);
|
|
595
|
-
const customSystemPrompt = await
|
|
594
|
+
const customSystemPrompt = await assistantConfigService.resolveSystemPrompt(
|
|
596
595
|
workspace,
|
|
597
596
|
{
|
|
598
|
-
surface:
|
|
597
|
+
surface: assistantRuntimeConfig.runtimeSurfaceId
|
|
599
598
|
},
|
|
600
599
|
{
|
|
601
|
-
context
|
|
600
|
+
context,
|
|
601
|
+
input: payload
|
|
602
602
|
}
|
|
603
603
|
);
|
|
604
604
|
const systemPrompt = buildSystemPrompt({
|
|
605
605
|
toolDescriptors: toolSet.tools,
|
|
606
|
-
workspaceSlug: resolveWorkspaceSlug(context,
|
|
606
|
+
workspaceSlug: resolveWorkspaceSlug(context, payload),
|
|
607
607
|
customSystemPrompt
|
|
608
608
|
});
|
|
609
609
|
|
|
@@ -634,7 +634,8 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
634
634
|
contentText: normalizedAssistantMessageText
|
|
635
635
|
},
|
|
636
636
|
{
|
|
637
|
-
context
|
|
637
|
+
context,
|
|
638
|
+
workspace
|
|
638
639
|
}
|
|
639
640
|
);
|
|
640
641
|
|
|
@@ -645,7 +646,8 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
645
646
|
metadata
|
|
646
647
|
},
|
|
647
648
|
{
|
|
648
|
-
context
|
|
649
|
+
context,
|
|
650
|
+
workspace
|
|
649
651
|
}
|
|
650
652
|
);
|
|
651
653
|
|
|
@@ -689,7 +691,8 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
689
691
|
}
|
|
690
692
|
},
|
|
691
693
|
{
|
|
692
|
-
context
|
|
694
|
+
context,
|
|
695
|
+
workspace
|
|
693
696
|
}
|
|
694
697
|
);
|
|
695
698
|
|
|
@@ -713,7 +716,8 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
713
716
|
}
|
|
714
717
|
},
|
|
715
718
|
{
|
|
716
|
-
context
|
|
719
|
+
context,
|
|
720
|
+
workspace
|
|
717
721
|
}
|
|
718
722
|
);
|
|
719
723
|
|
|
@@ -923,7 +927,6 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
923
927
|
} catch (error) {
|
|
924
928
|
const aborted = isAbortError(error);
|
|
925
929
|
const status = aborted ? "aborted" : "failed";
|
|
926
|
-
const streamError = mapStreamError(error);
|
|
927
930
|
|
|
928
931
|
if (streamed) {
|
|
929
932
|
await transcriptService.completeConversation(
|
|
@@ -932,16 +935,17 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
932
935
|
status
|
|
933
936
|
},
|
|
934
937
|
{
|
|
935
|
-
context
|
|
938
|
+
context,
|
|
939
|
+
workspace
|
|
936
940
|
}
|
|
937
941
|
);
|
|
938
942
|
|
|
939
943
|
streamWriter.sendError({
|
|
940
944
|
type: ASSISTANT_STREAM_EVENT_TYPES.ERROR,
|
|
941
945
|
messageId: source.messageId,
|
|
942
|
-
code: aborted ? "assistant_stream_aborted" :
|
|
943
|
-
message: aborted ? "Assistant request was cancelled." :
|
|
944
|
-
status: aborted ? 499 :
|
|
946
|
+
code: aborted ? "assistant_stream_aborted" : String(error?.code || "assistant_stream_failed"),
|
|
947
|
+
message: aborted ? "Assistant request was cancelled." : String(error?.message || "Assistant request failed."),
|
|
948
|
+
status: aborted ? 499 : Number(error?.status || error?.statusCode || 500)
|
|
945
949
|
});
|
|
946
950
|
|
|
947
951
|
streamWriter.sendDone({
|
|
@@ -963,14 +967,20 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
963
967
|
|
|
964
968
|
async function listConversations(query = {}, options = {}) {
|
|
965
969
|
const context = normalizeObject(options.context);
|
|
966
|
-
|
|
970
|
+
const workspace = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
|
|
971
|
+
? resolveWorkspace(context, options.input || {})
|
|
972
|
+
: null;
|
|
973
|
+
return transcriptService.listConversationsForUser(workspace, context.actor, query, {
|
|
967
974
|
context
|
|
968
975
|
});
|
|
969
976
|
}
|
|
970
977
|
|
|
971
978
|
async function getConversationMessages(conversationId, query = {}, options = {}) {
|
|
972
979
|
const context = normalizeObject(options.context);
|
|
973
|
-
|
|
980
|
+
const workspace = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
|
|
981
|
+
? resolveWorkspace(context, options.input || {})
|
|
982
|
+
: null;
|
|
983
|
+
return transcriptService.getConversationMessagesForUser(workspace, context.actor, conversationId, query, {
|
|
974
984
|
context
|
|
975
985
|
});
|
|
976
986
|
}
|
|
@@ -982,6 +992,4 @@ function createChatService({ aiClient, transcriptService, serviceToolCatalog, as
|
|
|
982
992
|
});
|
|
983
993
|
}
|
|
984
994
|
|
|
985
|
-
export {
|
|
986
|
-
createChatService
|
|
987
|
-
};
|
|
995
|
+
export { createChatService };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AppError, parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
2
2
|
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
-
import { normalizeConversationStatus } from "
|
|
3
|
+
import { normalizeConversationStatus } from "@jskit-ai/assistant-core/shared";
|
|
4
|
+
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
4
5
|
|
|
5
6
|
const DEFAULT_PAGE_SIZE = 20;
|
|
6
7
|
const MAX_PAGE_SIZE = 200;
|
|
@@ -9,64 +10,13 @@ const MAX_MESSAGES_PAGE_SIZE = 500;
|
|
|
9
10
|
const DEFAULT_CONVERSATION_TITLE = "New conversation";
|
|
10
11
|
const MAX_CONVERSATION_TITLE_LENGTH = 80;
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
createConversationForTurn: Object.freeze([
|
|
14
|
-
Object.freeze({
|
|
15
|
-
type: "entity.changed",
|
|
16
|
-
source: "assistant",
|
|
17
|
-
entity: "conversation",
|
|
18
|
-
operation: ({ result }) => (result?.created === true ? "created" : "updated"),
|
|
19
|
-
entityId: ({ result }) => result?.conversation?.id,
|
|
20
|
-
realtime: Object.freeze({
|
|
21
|
-
event: "assistant.transcript.changed",
|
|
22
|
-
audience: "event_scope",
|
|
23
|
-
payload: ({ result }) => ({
|
|
24
|
-
conversationId: result?.conversation?.id || null
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
]),
|
|
29
|
-
appendMessage: Object.freeze([
|
|
30
|
-
Object.freeze({
|
|
31
|
-
type: "entity.changed",
|
|
32
|
-
source: "assistant",
|
|
33
|
-
entity: "conversation",
|
|
34
|
-
operation: "updated",
|
|
35
|
-
entityId: ({ result }) => result?.conversationId,
|
|
36
|
-
realtime: Object.freeze({
|
|
37
|
-
event: "assistant.transcript.changed",
|
|
38
|
-
audience: "event_scope",
|
|
39
|
-
payload: ({ result }) => ({
|
|
40
|
-
conversationId: result?.conversationId || null
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
]),
|
|
45
|
-
completeConversation: Object.freeze([
|
|
46
|
-
Object.freeze({
|
|
47
|
-
type: "entity.changed",
|
|
48
|
-
source: "assistant",
|
|
49
|
-
entity: "conversation",
|
|
50
|
-
operation: "updated",
|
|
51
|
-
entityId: ({ result }) => result?.id,
|
|
52
|
-
realtime: Object.freeze({
|
|
53
|
-
event: "assistant.transcript.changed",
|
|
54
|
-
audience: "event_scope",
|
|
55
|
-
payload: ({ result }) => ({
|
|
56
|
-
conversationId: result?.id || null
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
])
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
function resolveWorkspaceId(workspace) {
|
|
13
|
+
function resolveWorkspaceId(workspace, { required = false } = {}) {
|
|
64
14
|
const workspaceId = parsePositiveInteger(workspace?.id || workspace);
|
|
65
|
-
if (!workspaceId) {
|
|
15
|
+
if (!workspaceId && required) {
|
|
66
16
|
throw new AppError(409, "Workspace selection required.");
|
|
67
17
|
}
|
|
68
18
|
|
|
69
|
-
return workspaceId;
|
|
19
|
+
return workspaceId || null;
|
|
70
20
|
}
|
|
71
21
|
|
|
72
22
|
function resolveActorUserId(user, { required = false } = {}) {
|
|
@@ -75,7 +25,7 @@ function resolveActorUserId(user, { required = false } = {}) {
|
|
|
75
25
|
throw new AppError(401, "Authentication required.");
|
|
76
26
|
}
|
|
77
27
|
|
|
78
|
-
return actorUserId;
|
|
28
|
+
return actorUserId || null;
|
|
79
29
|
}
|
|
80
30
|
|
|
81
31
|
function normalizePagination(pagination = {}, { defaultPageSize = DEFAULT_PAGE_SIZE, maxPageSize = MAX_PAGE_SIZE } = {}) {
|
|
@@ -116,8 +66,14 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
116
66
|
throw new Error("createTranscriptService requires conversationsRepository and messagesRepository.");
|
|
117
67
|
}
|
|
118
68
|
|
|
69
|
+
function resolveExpectedWorkspaceId(workspace) {
|
|
70
|
+
return resolveWorkspaceId(workspace, {
|
|
71
|
+
required: assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace === true
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
119
75
|
async function createConversationForTurn(workspace, user, options = {}) {
|
|
120
|
-
const workspaceId =
|
|
76
|
+
const workspaceId = resolveExpectedWorkspaceId(workspace);
|
|
121
77
|
const actorUserId = resolveActorUserId(user, {
|
|
122
78
|
required: true
|
|
123
79
|
});
|
|
@@ -125,7 +81,10 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
125
81
|
const conversationId = parsePositiveInteger(source.conversationId);
|
|
126
82
|
|
|
127
83
|
if (conversationId) {
|
|
128
|
-
const existing = await conversationsRepository.
|
|
84
|
+
const existing = await conversationsRepository.findByIdForActorScope(conversationId, {
|
|
85
|
+
workspaceId,
|
|
86
|
+
actorUserId
|
|
87
|
+
});
|
|
129
88
|
if (!existing) {
|
|
130
89
|
throw new AppError(404, "Conversation not found.");
|
|
131
90
|
}
|
|
@@ -155,7 +114,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
155
114
|
status: "active",
|
|
156
115
|
provider: normalizeText(source.provider),
|
|
157
116
|
model: normalizeText(source.model),
|
|
158
|
-
|
|
117
|
+
surfaceId: normalizeText(source.surfaceId).toLowerCase() || assistantRuntimeConfig.runtimeSurfaceId,
|
|
159
118
|
metadata: {
|
|
160
119
|
firstMessageId: normalizeText(source.messageId)
|
|
161
120
|
}
|
|
@@ -175,12 +134,16 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
175
134
|
|
|
176
135
|
const source = normalizeObject(payload);
|
|
177
136
|
const context = normalizeObject(options.context);
|
|
178
|
-
const
|
|
137
|
+
const actorUserId = parsePositiveInteger(source.actorUserId) || resolveActorUserId(context.actor);
|
|
138
|
+
const workspaceId = resolveExpectedWorkspaceId(options.workspace || context.workspace);
|
|
139
|
+
const conversation = await conversationsRepository.findByIdForActorScope(numericConversationId, {
|
|
140
|
+
workspaceId,
|
|
141
|
+
actorUserId
|
|
142
|
+
});
|
|
179
143
|
if (!conversation) {
|
|
180
144
|
throw new AppError(404, "Conversation not found.");
|
|
181
145
|
}
|
|
182
146
|
|
|
183
|
-
const actorUserId = parsePositiveInteger(source.actorUserId) || resolveActorUserId(context.actor) || null;
|
|
184
147
|
const createdMessage = await messagesRepository.create({
|
|
185
148
|
conversationId: numericConversationId,
|
|
186
149
|
workspaceId: conversation.workspaceId,
|
|
@@ -211,14 +174,22 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
211
174
|
};
|
|
212
175
|
}
|
|
213
176
|
|
|
214
|
-
async function completeConversation(conversationId, payload = {}) {
|
|
177
|
+
async function completeConversation(conversationId, payload = {}, options = {}) {
|
|
215
178
|
const numericConversationId = parsePositiveInteger(conversationId);
|
|
216
179
|
if (!numericConversationId) {
|
|
217
180
|
throw new TypeError("completeConversation requires conversationId.");
|
|
218
181
|
}
|
|
219
182
|
|
|
220
183
|
const source = normalizeObject(payload);
|
|
221
|
-
const
|
|
184
|
+
const context = normalizeObject(options.context);
|
|
185
|
+
const actorUserId = resolveActorUserId(context.actor, {
|
|
186
|
+
required: true
|
|
187
|
+
});
|
|
188
|
+
const workspaceId = resolveExpectedWorkspaceId(options.workspace || context.workspace);
|
|
189
|
+
const existing = await conversationsRepository.findByIdForActorScope(numericConversationId, {
|
|
190
|
+
workspaceId,
|
|
191
|
+
actorUserId
|
|
192
|
+
});
|
|
222
193
|
if (!existing) {
|
|
223
194
|
throw new AppError(404, "Conversation not found.");
|
|
224
195
|
}
|
|
@@ -236,7 +207,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
236
207
|
}
|
|
237
208
|
|
|
238
209
|
async function listConversationsForUser(workspace, user, query = {}) {
|
|
239
|
-
const workspaceId =
|
|
210
|
+
const workspaceId = resolveExpectedWorkspaceId(workspace);
|
|
240
211
|
const actorUserId = resolveActorUserId(user, {
|
|
241
212
|
required: true
|
|
242
213
|
});
|
|
@@ -252,19 +223,21 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
252
223
|
...(status ? { status } : {})
|
|
253
224
|
};
|
|
254
225
|
|
|
255
|
-
return conversationsRepository.
|
|
256
|
-
workspaceId,
|
|
257
|
-
actorUserId,
|
|
226
|
+
return conversationsRepository.listForActorScope(
|
|
258
227
|
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
228
|
+
workspaceId,
|
|
229
|
+
actorUserId,
|
|
230
|
+
pagination: {
|
|
231
|
+
cursor: pagination.cursor,
|
|
232
|
+
limit: pagination.limit
|
|
233
|
+
},
|
|
234
|
+
filters
|
|
235
|
+
}
|
|
263
236
|
);
|
|
264
237
|
}
|
|
265
238
|
|
|
266
239
|
async function getConversationMessagesForUser(workspace, user, conversationId, query = {}) {
|
|
267
|
-
const workspaceId =
|
|
240
|
+
const workspaceId = resolveExpectedWorkspaceId(workspace);
|
|
268
241
|
const actorUserId = resolveActorUserId(user, {
|
|
269
242
|
required: true
|
|
270
243
|
});
|
|
@@ -279,10 +252,12 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
279
252
|
});
|
|
280
253
|
}
|
|
281
254
|
|
|
282
|
-
const conversation = await conversationsRepository.
|
|
255
|
+
const conversation = await conversationsRepository.findByIdForActorScope(
|
|
283
256
|
numericConversationId,
|
|
284
|
-
|
|
285
|
-
|
|
257
|
+
{
|
|
258
|
+
workspaceId,
|
|
259
|
+
actorUserId
|
|
260
|
+
}
|
|
286
261
|
);
|
|
287
262
|
if (!conversation) {
|
|
288
263
|
throw new AppError(404, "Conversation not found.");
|
|
@@ -292,12 +267,19 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
292
267
|
defaultPageSize: DEFAULT_MESSAGES_PAGE_SIZE,
|
|
293
268
|
maxPageSize: MAX_MESSAGES_PAGE_SIZE
|
|
294
269
|
});
|
|
295
|
-
const total = await messagesRepository.
|
|
270
|
+
const total = await messagesRepository.countByConversationScope(
|
|
271
|
+
numericConversationId,
|
|
272
|
+
{
|
|
273
|
+
workspaceId
|
|
274
|
+
}
|
|
275
|
+
);
|
|
296
276
|
const totalPages = Math.max(1, Math.ceil(total / pagination.pageSize));
|
|
297
277
|
const page = Math.min(pagination.page, totalPages);
|
|
298
|
-
const entries = await messagesRepository.
|
|
278
|
+
const entries = await messagesRepository.listByConversationScope(
|
|
299
279
|
numericConversationId,
|
|
300
|
-
|
|
280
|
+
{
|
|
281
|
+
workspaceId
|
|
282
|
+
},
|
|
301
283
|
{
|
|
302
284
|
page,
|
|
303
285
|
pageSize: pagination.pageSize
|
|
@@ -323,7 +305,4 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
|
|
|
323
305
|
});
|
|
324
306
|
}
|
|
325
307
|
|
|
326
|
-
export {
|
|
327
|
-
createTranscriptService,
|
|
328
|
-
serviceEvents
|
|
329
|
-
};
|
|
308
|
+
export { createTranscriptService };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const assistantRuntimeConfig = Object.freeze({
|
|
2
|
+
runtimeSurfaceId: "__ASSISTANT_RUNTIME_SURFACE_ID__",
|
|
3
|
+
settingsSurfaceId: "__ASSISTANT_SETTINGS_SURFACE_ID__",
|
|
4
|
+
runtimeSurfaceRequiresWorkspace: __ASSISTANT_RUNTIME_SURFACE_REQUIRES_WORKSPACE__,
|
|
5
|
+
settingsSurfaceRequiresWorkspace: __ASSISTANT_SETTINGS_SURFACE_REQUIRES_WORKSPACE__,
|
|
6
|
+
settingsSurfaceRequiresConsoleOwner: __ASSISTANT_SETTINGS_SURFACE_REQUIRES_CONSOLE_OWNER__,
|
|
7
|
+
configScope: "__ASSISTANT_CONFIG_SCOPE__",
|
|
8
|
+
configTable: "__ASSISTANT_CONFIG_TABLE__",
|
|
9
|
+
conversationsTable: "__ASSISTANT_CONVERSATIONS_TABLE__",
|
|
10
|
+
messagesTable: "__ASSISTANT_MESSAGES_TABLE__"
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export { assistantRuntimeConfig };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { assistantRuntimeConfig } from "./assistantRuntimeConfig.js";
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { buildTemplateContext } from "../src/server/buildTemplateContext.js";
|
|
7
|
+
|
|
8
|
+
async function withTempApp(run) {
|
|
9
|
+
const appRoot = await mkdtemp(path.join(tmpdir(), "assistant-generator-"));
|
|
10
|
+
try {
|
|
11
|
+
await mkdir(path.join(appRoot, "config"), { recursive: true });
|
|
12
|
+
await mkdir(path.join(appRoot, "src", "components"), { recursive: true });
|
|
13
|
+
await writeFile(
|
|
14
|
+
path.join(appRoot, "package.json"),
|
|
15
|
+
`${JSON.stringify({ name: "assistant-generator-test-app", private: true, type: "module" }, null, 2)}\n`,
|
|
16
|
+
"utf8"
|
|
17
|
+
);
|
|
18
|
+
await writeFile(
|
|
19
|
+
path.join(appRoot, "config", "public.js"),
|
|
20
|
+
`export const config = {
|
|
21
|
+
surfaceDefinitions: {
|
|
22
|
+
app: { id: "app", enabled: true, requiresWorkspace: false, accessPolicyId: "authenticated" },
|
|
23
|
+
admin: { id: "admin", enabled: true, requiresWorkspace: true, accessPolicyId: "workspace_member" },
|
|
24
|
+
console: { id: "console", enabled: true, requiresWorkspace: false, accessPolicyId: "console_owner" }
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
`,
|
|
28
|
+
"utf8"
|
|
29
|
+
);
|
|
30
|
+
await writeFile(
|
|
31
|
+
path.join(appRoot, "src", "components", "ShellLayout.vue"),
|
|
32
|
+
`<template>
|
|
33
|
+
<div>
|
|
34
|
+
<ShellOutlet host="shell-layout" position="primary-menu" />
|
|
35
|
+
<ShellOutlet host="shell-layout" position="top-right" default />
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
`,
|
|
39
|
+
"utf8"
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return await run(appRoot);
|
|
43
|
+
} finally {
|
|
44
|
+
await rm(appRoot, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test("buildTemplateContext derives runtime, settings, and table placeholders from explicit surfaces", async () => {
|
|
49
|
+
await withTempApp(async (appRoot) => {
|
|
50
|
+
const context = await buildTemplateContext({
|
|
51
|
+
appRoot,
|
|
52
|
+
options: {
|
|
53
|
+
"runtime-surface": "app",
|
|
54
|
+
"settings-surface": "console",
|
|
55
|
+
"config-scope": "global",
|
|
56
|
+
placement: "shell-layout:primary-menu",
|
|
57
|
+
"menu-label": "Copilot"
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
assert.equal(context.__ASSISTANT_RUNTIME_SURFACE_ID__, "app");
|
|
62
|
+
assert.equal(context.__ASSISTANT_SETTINGS_SURFACE_ID__, "console");
|
|
63
|
+
assert.equal(context.__ASSISTANT_RUNTIME_SURFACE_REQUIRES_WORKSPACE__, "false");
|
|
64
|
+
assert.equal(context.__ASSISTANT_SETTINGS_SURFACE_REQUIRES_WORKSPACE__, "false");
|
|
65
|
+
assert.equal(context.__ASSISTANT_SETTINGS_SURFACE_REQUIRES_CONSOLE_OWNER__, "true");
|
|
66
|
+
assert.equal(context.__ASSISTANT_CONFIG_SCOPE__, "global");
|
|
67
|
+
assert.equal(context.__ASSISTANT_SETTINGS_HOST__, "console-settings");
|
|
68
|
+
assert.equal(context.__ASSISTANT_CONVERSATIONS_TABLE__, "assistant_app_conversations");
|
|
69
|
+
assert.equal(context.__ASSISTANT_MESSAGES_TABLE__, "assistant_app_messages");
|
|
70
|
+
assert.equal(context.__ASSISTANT_MENU_PLACEMENT_HOST__, "shell-layout");
|
|
71
|
+
assert.equal(context.__ASSISTANT_MENU_PLACEMENT_POSITION__, "primary-menu");
|
|
72
|
+
assert.equal(context.__ASSISTANT_MENU_LABEL__, "Copilot");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("buildTemplateContext rejects workspace config scope for non-workspace surfaces", async () => {
|
|
77
|
+
await withTempApp(async (appRoot) => {
|
|
78
|
+
await assert.rejects(
|
|
79
|
+
() =>
|
|
80
|
+
buildTemplateContext({
|
|
81
|
+
appRoot,
|
|
82
|
+
options: {
|
|
83
|
+
"runtime-surface": "app",
|
|
84
|
+
"settings-surface": "console",
|
|
85
|
+
"config-scope": "workspace",
|
|
86
|
+
placement: "shell-layout:primary-menu"
|
|
87
|
+
}
|
|
88
|
+
}),
|
|
89
|
+
/config-scope "workspace" requires runtime surface "app" with requiresWorkspace=true/
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("buildTemplateContext accepts workspace config scope when both surfaces require workspace", async () => {
|
|
95
|
+
await withTempApp(async (appRoot) => {
|
|
96
|
+
const context = await buildTemplateContext({
|
|
97
|
+
appRoot,
|
|
98
|
+
options: {
|
|
99
|
+
"runtime-surface": "admin",
|
|
100
|
+
"settings-surface": "admin",
|
|
101
|
+
"config-scope": "workspace",
|
|
102
|
+
placement: "shell-layout:top-right"
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
assert.equal(context.__ASSISTANT_RUNTIME_SURFACE_REQUIRES_WORKSPACE__, "true");
|
|
107
|
+
assert.equal(context.__ASSISTANT_SETTINGS_SURFACE_REQUIRES_WORKSPACE__, "true");
|
|
108
|
+
assert.equal(context.__ASSISTANT_SETTINGS_HOST__, "admin-settings");
|
|
109
|
+
assert.equal(context.__ASSISTANT_CONVERSATIONS_TABLE__, "assistant_admin_conversations");
|
|
110
|
+
assert.equal(context.__ASSISTANT_MESSAGES_TABLE__, "assistant_admin_messages");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import descriptor from "../package.descriptor.mjs";
|
|
4
|
+
|
|
5
|
+
function findFileMutation(id) {
|
|
6
|
+
const mutations = Array.isArray(descriptor?.mutations?.files) ? descriptor.mutations.files : [];
|
|
7
|
+
return mutations.find((entry) => String(entry?.id || "") === id) || null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function findTextMutation(id) {
|
|
11
|
+
const mutations = Array.isArray(descriptor?.mutations?.text) ? descriptor.mutations.text : [];
|
|
12
|
+
return mutations.find((entry) => String(entry?.id || "") === id) || null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test("assistant descriptor exposes generator options for runtime surface, settings surface, and config scope", () => {
|
|
16
|
+
assert.equal(descriptor.kind, "generator");
|
|
17
|
+
assert.equal(descriptor.metadata?.generatorPrimarySubcommand, "install");
|
|
18
|
+
assert.equal(descriptor.options?.["runtime-surface"]?.required, true);
|
|
19
|
+
assert.equal(descriptor.options?.["settings-surface"]?.required, true);
|
|
20
|
+
assert.equal(descriptor.options?.["config-scope"]?.defaultValue, "global");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("assistant descriptor publishes install examples for the supported runtime/settings shapes", () => {
|
|
24
|
+
const installMetadata = descriptor.metadata?.generatorSubcommands?.install || {};
|
|
25
|
+
const examples = Array.isArray(installMetadata.examples) ? installMetadata.examples : [];
|
|
26
|
+
const labels = examples.map((entry) => String(entry?.label || ""));
|
|
27
|
+
|
|
28
|
+
assert.equal(examples.length, 4);
|
|
29
|
+
assert.deepEqual(labels, [
|
|
30
|
+
"App runtime, console settings, global config",
|
|
31
|
+
"App runtime, app settings, global config",
|
|
32
|
+
"Workspace runtime, console settings, global config",
|
|
33
|
+
"Workspace runtime, workspace settings, workspace config"
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("assistant descriptor installs generated local runtime files instead of shipping a framework runtime", () => {
|
|
38
|
+
const runtimePage = findFileMutation("assistant-page-runtime");
|
|
39
|
+
const localPackageDescriptor = findFileMutation("assistant-local-package-descriptor");
|
|
40
|
+
const configMigration = findFileMutation("assistant-config-initial-schema");
|
|
41
|
+
const transcriptMigration = findFileMutation("assistant-transcripts-initial-schema");
|
|
42
|
+
|
|
43
|
+
assert.equal(runtimePage?.toSurface, "${option:runtime-surface|lower}");
|
|
44
|
+
assert.equal(runtimePage?.toSurfacePath, "assistant/index.vue");
|
|
45
|
+
assert.equal(localPackageDescriptor?.to, "packages/assistant/package.descriptor.mjs");
|
|
46
|
+
assert.equal(configMigration?.from, "templates/migrations/assistant_config_initial.cjs");
|
|
47
|
+
assert.equal(transcriptMigration?.from, "templates/migrations/assistant_transcripts_initial.cjs");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("assistant descriptor targets runtime and settings placements through explicit surface options", () => {
|
|
51
|
+
const menuPlacement = findTextMutation("assistant-placement-menu");
|
|
52
|
+
const settingsPlacement = findTextMutation("assistant-settings-form-placement");
|
|
53
|
+
|
|
54
|
+
assert.match(String(menuPlacement?.value || ""), /surfaces: \["\$\{option:runtime-surface\|lower\}"\]/);
|
|
55
|
+
assert.match(String(menuPlacement?.value || ""), /workspaceSuffix: "__ASSISTANT_MENU_WORKSPACE_SUFFIX__"/);
|
|
56
|
+
assert.match(String(menuPlacement?.value || ""), /nonWorkspaceSuffix: "__ASSISTANT_MENU_NON_WORKSPACE_SUFFIX__"/);
|
|
57
|
+
assert.match(String(settingsPlacement?.value || ""), /host: "__ASSISTANT_SETTINGS_HOST__"/);
|
|
58
|
+
assert.match(String(settingsPlacement?.value || ""), /surfaces: \["\$\{option:settings-surface\|lower\}"\]/);
|
|
59
|
+
assert.match(String(settingsPlacement?.value || ""), /componentToken: "assistant.web.settings.element"/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("assistant descriptor runtime deps use assistant-core and avoid workspace package coupling", () => {
|
|
63
|
+
const runtimeDeps = descriptor?.mutations?.dependencies?.runtime || {};
|
|
64
|
+
|
|
65
|
+
assert.equal(runtimeDeps["@local/assistant"], "file:packages/assistant");
|
|
66
|
+
assert.equal(runtimeDeps["@jskit-ai/assistant-core"], "0.1.0");
|
|
67
|
+
assert.equal(Object.hasOwn(runtimeDeps, "@jskit-ai/workspaces-core"), false);
|
|
68
|
+
assert.equal(Object.hasOwn(runtimeDeps, "@jskit-ai/workspaces-web"), false);
|
|
69
|
+
});
|