@jskit-ai/assistant 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 +284 -0
- package/package.json +31 -0
- package/src/client/components/AssistantClientElement.vue +1316 -0
- package/src/client/components/AssistantConsoleSettingsClientElement.vue +71 -0
- package/src/client/components/AssistantSettingsFormCard.vue +76 -0
- package/src/client/components/AssistantWorkspaceClientElement.vue +15 -0
- package/src/client/components/AssistantWorkspaceSettingsClientElement.vue +73 -0
- package/src/client/composables/useAssistantWorkspaceRuntime.js +789 -0
- package/src/client/index.js +12 -0
- package/src/client/lib/assistantApi.js +137 -0
- package/src/client/lib/assistantHttpClient.js +10 -0
- package/src/client/lib/markdownRenderer.js +31 -0
- package/src/client/providers/AssistantWebClientProvider.js +25 -0
- package/src/server/AssistantServiceProvider.js +179 -0
- package/src/server/actionIds.js +11 -0
- package/src/server/actions.js +191 -0
- package/src/server/diTokens.js +19 -0
- package/src/server/lib/aiClient.js +43 -0
- package/src/server/lib/ndjson.js +47 -0
- package/src/server/lib/providers/anthropicClient.js +375 -0
- package/src/server/lib/providers/common.js +158 -0
- package/src/server/lib/providers/deepSeekClient.js +22 -0
- package/src/server/lib/providers/openAiClient.js +13 -0
- package/src/server/lib/providers/openAiCompatibleClient.js +69 -0
- package/src/server/lib/resolveWorkspaceSlug.js +24 -0
- package/src/server/lib/serviceToolCatalog.js +459 -0
- package/src/server/registerRoutes.js +384 -0
- package/src/server/repositories/assistantSettingsRepository.js +100 -0
- package/src/server/repositories/conversationsRepository.js +244 -0
- package/src/server/repositories/messagesRepository.js +154 -0
- package/src/server/repositories/repositoryPersistenceUtils.js +63 -0
- package/src/server/services/assistantSettingsService.js +153 -0
- package/src/server/services/chatService.js +987 -0
- package/src/server/services/transcriptService.js +334 -0
- package/src/shared/assistantPaths.js +50 -0
- package/src/shared/assistantResource.js +323 -0
- package/src/shared/assistantSettingsResource.js +214 -0
- package/src/shared/index.js +39 -0
- package/src/shared/queryKeys.js +69 -0
- package/src/shared/settingsEvents.js +7 -0
- package/src/shared/streamEvents.js +31 -0
- package/src/shared/support/positiveInteger.js +9 -0
- package/templates/migrations/assistant_settings_initial.cjs +39 -0
- package/templates/migrations/assistant_transcripts_initial.cjs +51 -0
- package/templates/src/pages/admin/workspace/assistant/index.vue +7 -0
- package/test/aiConfigValidation.test.js +15 -0
- package/test/assistantApiSurfaceHeader.test.js +64 -0
- package/test/assistantResource.test.js +53 -0
- package/test/assistantSettingsResource.test.js +48 -0
- package/test/assistantSettingsService.test.js +133 -0
- package/test/chatService.test.js +841 -0
- package/test/descriptorSurfaceOption.test.js +35 -0
- package/test/queryKeys.test.js +41 -0
- package/test/resolveWorkspaceSlug.test.js +83 -0
- package/test/routeInputContracts.test.js +287 -0
- package/test/serviceToolCatalog.test.js +1235 -0
- package/test/transcriptService.test.js +175 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { default as AssistantClientElement } from "./components/AssistantClientElement.vue";
|
|
2
|
+
export { default as AssistantWorkspaceClientElement } from "./components/AssistantWorkspaceClientElement.vue";
|
|
3
|
+
export { default as AssistantConsoleSettingsClientElement } from "./components/AssistantConsoleSettingsClientElement.vue";
|
|
4
|
+
export { default as AssistantWorkspaceSettingsClientElement } from "./components/AssistantWorkspaceSettingsClientElement.vue";
|
|
5
|
+
export { useAssistantWorkspaceRuntime } from "./composables/useAssistantWorkspaceRuntime.js";
|
|
6
|
+
export { assistantHttpClient } from "./lib/assistantHttpClient.js";
|
|
7
|
+
export { createAssistantWorkspaceApi, buildStreamEventError } from "./lib/assistantApi.js";
|
|
8
|
+
export {
|
|
9
|
+
AssistantWebClientProvider,
|
|
10
|
+
ASSISTANT_CONSOLE_SETTINGS_ELEMENT_TOKEN,
|
|
11
|
+
ASSISTANT_WORKSPACE_SETTINGS_ELEMENT_TOKEN
|
|
12
|
+
} from "./providers/AssistantWebClientProvider.js";
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ASSISTANT_STREAM_EVENT_TYPES,
|
|
3
|
+
buildAssistantWorkspaceApiPath,
|
|
4
|
+
normalizeAssistantStreamEventType
|
|
5
|
+
} from "../../shared/index.js";
|
|
6
|
+
import { appendQueryString } from "@jskit-ai/kernel/shared/support";
|
|
7
|
+
|
|
8
|
+
function buildStreamEventError(event) {
|
|
9
|
+
const message = String(event?.message || "Assistant request failed.");
|
|
10
|
+
const error = new Error(message);
|
|
11
|
+
error.code = String(event?.code || "assistant_stream_error");
|
|
12
|
+
error.status = Number(event?.status || 500);
|
|
13
|
+
error.event = event && typeof event === "object" ? { ...event } : null;
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function appendQueryParam(params, key, value) {
|
|
18
|
+
if (value == null) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const normalized = String(value).trim();
|
|
23
|
+
if (!normalized) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
params.set(key, normalized);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveWorkspaceBasePath(workspaceSlug = "") {
|
|
31
|
+
const path = buildAssistantWorkspaceApiPath(workspaceSlug, "/");
|
|
32
|
+
if (!path) {
|
|
33
|
+
throw new Error("Assistant workspace slug is required.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return path;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeSurfaceHeaderValue(value) {
|
|
40
|
+
return String(value || "").trim().toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveAssistantRequestHeaders(resolveSurfaceId) {
|
|
44
|
+
if (typeof resolveSurfaceId !== "function") {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const surfaceId = normalizeSurfaceHeaderValue(resolveSurfaceId());
|
|
49
|
+
if (!surfaceId) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"x-jskit-surface": surfaceId
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createAssistantWorkspaceApi({ request, requestStream, resolveSurfaceId = null }) {
|
|
59
|
+
if (typeof request !== "function" || typeof requestStream !== "function") {
|
|
60
|
+
throw new Error("createAssistantWorkspaceApi requires request() and requestStream().");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Object.freeze({
|
|
64
|
+
async streamChat(workspaceSlug, payload, { signal, onEvent, onMalformedLine, rejectOnErrorEvent = true } = {}) {
|
|
65
|
+
const basePath = resolveWorkspaceBasePath(workspaceSlug);
|
|
66
|
+
let streamEventError = null;
|
|
67
|
+
const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
|
|
68
|
+
|
|
69
|
+
const streamHandlers = {
|
|
70
|
+
onEvent(event) {
|
|
71
|
+
const eventType = normalizeAssistantStreamEventType(event?.type, "");
|
|
72
|
+
if (rejectOnErrorEvent && eventType === ASSISTANT_STREAM_EVENT_TYPES.ERROR && !streamEventError) {
|
|
73
|
+
streamEventError = buildStreamEventError(event);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof onEvent === "function") {
|
|
77
|
+
onEvent(event);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (typeof onMalformedLine === "function") {
|
|
83
|
+
streamHandlers.onMalformedLine = (line, parseError) => {
|
|
84
|
+
onMalformedLine(line, parseError);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await requestStream(
|
|
89
|
+
`${basePath}/chat/stream`,
|
|
90
|
+
{
|
|
91
|
+
method: "POST",
|
|
92
|
+
...(requestHeaders ? { headers: requestHeaders } : {}),
|
|
93
|
+
body: payload,
|
|
94
|
+
signal
|
|
95
|
+
},
|
|
96
|
+
streamHandlers
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (streamEventError) {
|
|
100
|
+
throw streamEventError;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
listConversations(workspaceSlug, query = {}) {
|
|
105
|
+
const basePath = resolveWorkspaceBasePath(workspaceSlug);
|
|
106
|
+
const params = new URLSearchParams();
|
|
107
|
+
appendQueryParam(params, "cursor", query.cursor);
|
|
108
|
+
appendQueryParam(params, "limit", query.limit);
|
|
109
|
+
appendQueryParam(params, "status", query.status);
|
|
110
|
+
const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
|
|
111
|
+
|
|
112
|
+
return request(
|
|
113
|
+
appendQueryString(`${basePath}/conversations`, params.toString()),
|
|
114
|
+
requestHeaders ? { headers: requestHeaders } : {}
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
getConversationMessages(workspaceSlug, conversationId, query = {}) {
|
|
119
|
+
const basePath = resolveWorkspaceBasePath(workspaceSlug);
|
|
120
|
+
const encodedConversationId = encodeURIComponent(String(conversationId || "").trim());
|
|
121
|
+
const params = new URLSearchParams();
|
|
122
|
+
appendQueryParam(params, "page", query.page);
|
|
123
|
+
appendQueryParam(params, "pageSize", query.pageSize);
|
|
124
|
+
const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
|
|
125
|
+
|
|
126
|
+
return request(
|
|
127
|
+
appendQueryString(`${basePath}/conversations/${encodedConversationId}/messages`, params.toString()),
|
|
128
|
+
requestHeaders ? { headers: requestHeaders } : {}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
createAssistantWorkspaceApi,
|
|
136
|
+
buildStreamEventError
|
|
137
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import DOMPurify from "dompurify";
|
|
2
|
+
import { marked } from "marked";
|
|
3
|
+
|
|
4
|
+
marked.setOptions(
|
|
5
|
+
Object.freeze({
|
|
6
|
+
gfm: true,
|
|
7
|
+
breaks: true
|
|
8
|
+
})
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
function normalizeMarkdownText(value) {
|
|
12
|
+
return typeof value === "string" ? value : String(value || "");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function renderMarkdownToSafeHtml(value = "") {
|
|
16
|
+
const markdownText = normalizeMarkdownText(value);
|
|
17
|
+
if (!markdownText) {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const unsafeHtml = String(marked.parse(markdownText) || "");
|
|
22
|
+
return String(
|
|
23
|
+
DOMPurify.sanitize(unsafeHtml, {
|
|
24
|
+
USE_PROFILES: {
|
|
25
|
+
html: true
|
|
26
|
+
}
|
|
27
|
+
}) || ""
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { renderMarkdownToSafeHtml };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import AssistantConsoleSettingsClientElement from "../components/AssistantConsoleSettingsClientElement.vue";
|
|
2
|
+
import AssistantWorkspaceSettingsClientElement from "../components/AssistantWorkspaceSettingsClientElement.vue";
|
|
3
|
+
|
|
4
|
+
const ASSISTANT_CONSOLE_SETTINGS_ELEMENT_TOKEN = "assistant.web.console-settings.element";
|
|
5
|
+
const ASSISTANT_WORKSPACE_SETTINGS_ELEMENT_TOKEN = "assistant.web.workspace-settings.element";
|
|
6
|
+
|
|
7
|
+
class AssistantWebClientProvider {
|
|
8
|
+
static id = "assistant.web.client";
|
|
9
|
+
static dependsOn = ["users.web.client"];
|
|
10
|
+
|
|
11
|
+
register(app) {
|
|
12
|
+
if (!app || typeof app.singleton !== "function") {
|
|
13
|
+
throw new Error("AssistantWebClientProvider requires application singleton().");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
app.singleton(ASSISTANT_CONSOLE_SETTINGS_ELEMENT_TOKEN, () => AssistantConsoleSettingsClientElement);
|
|
17
|
+
app.singleton(ASSISTANT_WORKSPACE_SETTINGS_ELEMENT_TOKEN, () => AssistantWorkspaceSettingsClientElement);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
AssistantWebClientProvider,
|
|
23
|
+
ASSISTANT_CONSOLE_SETTINGS_ELEMENT_TOKEN,
|
|
24
|
+
ASSISTANT_WORKSPACE_SETTINGS_ELEMENT_TOKEN
|
|
25
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { KERNEL_TOKENS } from "@jskit-ai/kernel/shared/support/tokens";
|
|
2
|
+
import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
|
|
3
|
+
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
import { createRepository as createConversationsRepository } from "./repositories/conversationsRepository.js";
|
|
5
|
+
import { createRepository as createMessagesRepository } from "./repositories/messagesRepository.js";
|
|
6
|
+
import { createRepository as createAssistantSettingsRepository } from "./repositories/assistantSettingsRepository.js";
|
|
7
|
+
import { createChatService } from "./services/chatService.js";
|
|
8
|
+
import {
|
|
9
|
+
createService as createAssistantSettingsService,
|
|
10
|
+
serviceEvents as assistantSettingsServiceEvents
|
|
11
|
+
} from "./services/assistantSettingsService.js";
|
|
12
|
+
import {
|
|
13
|
+
createTranscriptService,
|
|
14
|
+
serviceEvents as transcriptServiceEvents
|
|
15
|
+
} from "./services/transcriptService.js";
|
|
16
|
+
import { createAiClient } from "./lib/aiClient.js";
|
|
17
|
+
import { createServiceToolCatalog } from "./lib/serviceToolCatalog.js";
|
|
18
|
+
import { normalizeOptionalHttpUrl } from "./lib/providers/common.js";
|
|
19
|
+
import { assistantActions } from "./actions.js";
|
|
20
|
+
import { registerRoutes } from "./registerRoutes.js";
|
|
21
|
+
import {
|
|
22
|
+
ASSISTANT_CONVERSATIONS_REPOSITORY_TOKEN,
|
|
23
|
+
ASSISTANT_MESSAGES_REPOSITORY_TOKEN,
|
|
24
|
+
ASSISTANT_SETTINGS_REPOSITORY_TOKEN,
|
|
25
|
+
ASSISTANT_TRANSCRIPT_SERVICE_TOKEN,
|
|
26
|
+
ASSISTANT_CHAT_SERVICE_TOKEN,
|
|
27
|
+
ASSISTANT_SETTINGS_SERVICE_TOKEN,
|
|
28
|
+
ASSISTANT_AI_CLIENT_TOKEN,
|
|
29
|
+
ASSISTANT_SERVICE_TOOL_CATALOG_TOKEN
|
|
30
|
+
} from "./diTokens.js";
|
|
31
|
+
|
|
32
|
+
const ASSISTANT_PROVIDER_ID = ASSISTANT_CHAT_SERVICE_TOKEN;
|
|
33
|
+
const DEFAULT_AI_TIMEOUT_MS = 120_000;
|
|
34
|
+
|
|
35
|
+
function normalizeInteger(value, fallback) {
|
|
36
|
+
const parsed = Number(value);
|
|
37
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeStringArray(value) {
|
|
45
|
+
const source = Array.isArray(value) ? value : [value];
|
|
46
|
+
return source.map((entry) => normalizeText(entry)).filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveAssistantConfig(scope) {
|
|
50
|
+
const appConfig = scope.has("appConfig") ? normalizeObject(scope.make("appConfig")) : {};
|
|
51
|
+
const env = scope.has(KERNEL_TOKENS.Env) ? normalizeObject(scope.make(KERNEL_TOKENS.Env)) : {};
|
|
52
|
+
const assistantConfig = normalizeObject(appConfig.assistant);
|
|
53
|
+
|
|
54
|
+
const provider = normalizeText(env.AI_PROVIDER || assistantConfig.provider).toLowerCase() || "openai";
|
|
55
|
+
const apiKey = normalizeText(env.AI_API_KEY || assistantConfig.apiKey);
|
|
56
|
+
const baseUrl = normalizeOptionalHttpUrl(env.AI_BASE_URL || assistantConfig.baseUrl, {
|
|
57
|
+
context: "assistant AI_BASE_URL"
|
|
58
|
+
});
|
|
59
|
+
const model = normalizeText(assistantConfig.model);
|
|
60
|
+
const timeoutMs = normalizeInteger(
|
|
61
|
+
env.AI_TIMEOUT_MS || assistantConfig.timeoutMs,
|
|
62
|
+
DEFAULT_AI_TIMEOUT_MS
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return Object.freeze({
|
|
66
|
+
ai: Object.freeze({
|
|
67
|
+
enabled: Boolean(apiKey),
|
|
68
|
+
provider,
|
|
69
|
+
apiKey,
|
|
70
|
+
baseUrl,
|
|
71
|
+
model,
|
|
72
|
+
timeoutMs
|
|
73
|
+
}),
|
|
74
|
+
timeoutMs,
|
|
75
|
+
barredActionIds: normalizeStringArray(assistantConfig.barredActionIds),
|
|
76
|
+
toolSkipActionPrefixes: normalizeStringArray(assistantConfig.toolSkipActionPrefixes)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class AssistantServiceProvider {
|
|
81
|
+
static id = ASSISTANT_PROVIDER_ID;
|
|
82
|
+
|
|
83
|
+
static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "users.core", "runtime.realtime"];
|
|
84
|
+
|
|
85
|
+
register(app) {
|
|
86
|
+
if (
|
|
87
|
+
!app ||
|
|
88
|
+
typeof app.singleton !== "function" ||
|
|
89
|
+
typeof app.service !== "function" ||
|
|
90
|
+
typeof app.actions !== "function"
|
|
91
|
+
) {
|
|
92
|
+
throw new Error("AssistantServiceProvider requires application singleton()/service()/actions().");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const config = resolveAssistantConfig(app);
|
|
96
|
+
|
|
97
|
+
app.singleton(ASSISTANT_CONVERSATIONS_REPOSITORY_TOKEN, (scope) => {
|
|
98
|
+
const knex = scope.make(KERNEL_TOKENS.Knex);
|
|
99
|
+
return createConversationsRepository(knex);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
app.singleton(ASSISTANT_MESSAGES_REPOSITORY_TOKEN, (scope) => {
|
|
103
|
+
const knex = scope.make(KERNEL_TOKENS.Knex);
|
|
104
|
+
return createMessagesRepository(knex);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
app.singleton(ASSISTANT_SETTINGS_REPOSITORY_TOKEN, (scope) => {
|
|
108
|
+
const knex = scope.make(KERNEL_TOKENS.Knex);
|
|
109
|
+
return createAssistantSettingsRepository(knex);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
app.singleton(ASSISTANT_AI_CLIENT_TOKEN, () => {
|
|
113
|
+
return createAiClient(config.ai);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
app.singleton(ASSISTANT_SERVICE_TOOL_CATALOG_TOKEN, (scope) => {
|
|
117
|
+
const skipPrefixes = ["assistant.", ...config.toolSkipActionPrefixes];
|
|
118
|
+
|
|
119
|
+
return createServiceToolCatalog(scope, {
|
|
120
|
+
barredActionIds: config.barredActionIds,
|
|
121
|
+
skipActionPrefixes: skipPrefixes
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
app.service(
|
|
126
|
+
ASSISTANT_TRANSCRIPT_SERVICE_TOKEN,
|
|
127
|
+
(scope) =>
|
|
128
|
+
createTranscriptService({
|
|
129
|
+
conversationsRepository: scope.make(ASSISTANT_CONVERSATIONS_REPOSITORY_TOKEN),
|
|
130
|
+
messagesRepository: scope.make(ASSISTANT_MESSAGES_REPOSITORY_TOKEN)
|
|
131
|
+
}),
|
|
132
|
+
{
|
|
133
|
+
events: transcriptServiceEvents
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
app.service(
|
|
138
|
+
ASSISTANT_SETTINGS_SERVICE_TOKEN,
|
|
139
|
+
(scope) =>
|
|
140
|
+
createAssistantSettingsService({
|
|
141
|
+
assistantSettingsRepository: scope.make(ASSISTANT_SETTINGS_REPOSITORY_TOKEN),
|
|
142
|
+
consoleService: scope.make("consoleService")
|
|
143
|
+
}),
|
|
144
|
+
{
|
|
145
|
+
events: assistantSettingsServiceEvents
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
app.service(
|
|
150
|
+
ASSISTANT_CHAT_SERVICE_TOKEN,
|
|
151
|
+
(scope) =>
|
|
152
|
+
createChatService({
|
|
153
|
+
aiClient: scope.make(ASSISTANT_AI_CLIENT_TOKEN),
|
|
154
|
+
transcriptService: scope.make(ASSISTANT_TRANSCRIPT_SERVICE_TOKEN),
|
|
155
|
+
serviceToolCatalog: scope.make(ASSISTANT_SERVICE_TOOL_CATALOG_TOKEN),
|
|
156
|
+
assistantSettingsService: scope.make(ASSISTANT_SETTINGS_SERVICE_TOKEN)
|
|
157
|
+
}),
|
|
158
|
+
{
|
|
159
|
+
events: {}
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
app.actions(
|
|
164
|
+
withActionDefaults(assistantActions, {
|
|
165
|
+
domain: "assistant",
|
|
166
|
+
dependencies: {
|
|
167
|
+
chatService: ASSISTANT_CHAT_SERVICE_TOKEN,
|
|
168
|
+
assistantSettingsService: ASSISTANT_SETTINGS_SERVICE_TOKEN
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
boot(app) {
|
|
175
|
+
registerRoutes(app);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { AssistantServiceProvider };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const actionIds = Object.freeze({
|
|
2
|
+
chatStream: "assistant.chat.stream",
|
|
3
|
+
conversationsList: "assistant.conversations.list",
|
|
4
|
+
conversationMessagesList: "assistant.conversation.messages.list",
|
|
5
|
+
consoleSettingsRead: "assistant.console.settings.read",
|
|
6
|
+
consoleSettingsUpdate: "assistant.console.settings.update",
|
|
7
|
+
workspaceSettingsRead: "assistant.workspace.settings.read",
|
|
8
|
+
workspaceSettingsUpdate: "assistant.workspace.settings.update"
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export { actionIds };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { workspaceSlugParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
|
|
2
|
+
import { resolveWorkspace } from "@jskit-ai/users-core/server/support/resolveWorkspace";
|
|
3
|
+
import {
|
|
4
|
+
EMPTY_INPUT_VALIDATOR
|
|
5
|
+
} from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
|
|
6
|
+
import { assistantResource } from "../shared/assistantResource.js";
|
|
7
|
+
import {
|
|
8
|
+
assistantConsoleSettingsResource,
|
|
9
|
+
assistantWorkspaceSettingsResource
|
|
10
|
+
} from "../shared/assistantSettingsResource.js";
|
|
11
|
+
import { actionIds } from "./actionIds.js";
|
|
12
|
+
|
|
13
|
+
const assistantActions = Object.freeze([
|
|
14
|
+
{
|
|
15
|
+
id: actionIds.chatStream,
|
|
16
|
+
version: 1,
|
|
17
|
+
kind: "stream",
|
|
18
|
+
channels: ["api", "internal"],
|
|
19
|
+
surfacesFrom: "workspace",
|
|
20
|
+
permission: {
|
|
21
|
+
require: "authenticated"
|
|
22
|
+
},
|
|
23
|
+
inputValidator: [workspaceSlugParamsValidator, assistantResource.operations.chatStream.bodyValidator],
|
|
24
|
+
idempotency: "optional",
|
|
25
|
+
audit: {
|
|
26
|
+
actionName: actionIds.chatStream
|
|
27
|
+
},
|
|
28
|
+
observability: {},
|
|
29
|
+
async execute(input, context, deps) {
|
|
30
|
+
return deps.chatService.streamChat(input, {
|
|
31
|
+
context,
|
|
32
|
+
streamWriter: deps.streamWriter,
|
|
33
|
+
abortSignal: deps.abortSignal
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: actionIds.conversationsList,
|
|
39
|
+
version: 1,
|
|
40
|
+
kind: "query",
|
|
41
|
+
channels: ["api", "internal"],
|
|
42
|
+
surfacesFrom: "workspace",
|
|
43
|
+
permission: {
|
|
44
|
+
require: "authenticated"
|
|
45
|
+
},
|
|
46
|
+
inputValidator: [
|
|
47
|
+
workspaceSlugParamsValidator,
|
|
48
|
+
{
|
|
49
|
+
query: assistantResource.operations.conversationsList.queryValidator
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
outputValidator: assistantResource.operations.conversationsList.outputValidator,
|
|
53
|
+
idempotency: "none",
|
|
54
|
+
audit: {
|
|
55
|
+
actionName: actionIds.conversationsList
|
|
56
|
+
},
|
|
57
|
+
observability: {},
|
|
58
|
+
async execute(input, context, deps) {
|
|
59
|
+
return deps.chatService.listConversations(input.query, {
|
|
60
|
+
context
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: actionIds.conversationMessagesList,
|
|
66
|
+
version: 1,
|
|
67
|
+
kind: "query",
|
|
68
|
+
channels: ["api", "internal"],
|
|
69
|
+
surfacesFrom: "workspace",
|
|
70
|
+
permission: {
|
|
71
|
+
require: "authenticated"
|
|
72
|
+
},
|
|
73
|
+
inputValidator: [
|
|
74
|
+
workspaceSlugParamsValidator,
|
|
75
|
+
assistantResource.operations.conversationMessagesList.paramsValidator,
|
|
76
|
+
{
|
|
77
|
+
query: assistantResource.operations.conversationMessagesList.queryValidator
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
outputValidator: assistantResource.operations.conversationMessagesList.outputValidator,
|
|
81
|
+
idempotency: "none",
|
|
82
|
+
audit: {
|
|
83
|
+
actionName: actionIds.conversationMessagesList
|
|
84
|
+
},
|
|
85
|
+
observability: {},
|
|
86
|
+
async execute(input, context, deps) {
|
|
87
|
+
return deps.chatService.getConversationMessages(input.conversationId, input.query, {
|
|
88
|
+
context
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: actionIds.consoleSettingsRead,
|
|
94
|
+
version: 1,
|
|
95
|
+
kind: "query",
|
|
96
|
+
channels: ["api", "automation", "internal"],
|
|
97
|
+
surfacesFrom: "console",
|
|
98
|
+
permission: {
|
|
99
|
+
require: "authenticated"
|
|
100
|
+
},
|
|
101
|
+
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
102
|
+
outputValidator: assistantConsoleSettingsResource.operations.view.outputValidator,
|
|
103
|
+
idempotency: "none",
|
|
104
|
+
audit: {
|
|
105
|
+
actionName: actionIds.consoleSettingsRead
|
|
106
|
+
},
|
|
107
|
+
observability: {},
|
|
108
|
+
async execute(_input, context, deps) {
|
|
109
|
+
return deps.assistantSettingsService.getConsoleSettings({
|
|
110
|
+
context
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: actionIds.consoleSettingsUpdate,
|
|
116
|
+
version: 1,
|
|
117
|
+
kind: "command",
|
|
118
|
+
channels: ["api", "automation", "internal"],
|
|
119
|
+
surfacesFrom: "console",
|
|
120
|
+
permission: {
|
|
121
|
+
require: "authenticated"
|
|
122
|
+
},
|
|
123
|
+
inputValidator: {
|
|
124
|
+
payload: assistantConsoleSettingsResource.operations.patch.bodyValidator
|
|
125
|
+
},
|
|
126
|
+
outputValidator: assistantConsoleSettingsResource.operations.patch.outputValidator,
|
|
127
|
+
idempotency: "optional",
|
|
128
|
+
audit: {
|
|
129
|
+
actionName: actionIds.consoleSettingsUpdate
|
|
130
|
+
},
|
|
131
|
+
observability: {},
|
|
132
|
+
async execute(input, context, deps) {
|
|
133
|
+
return deps.assistantSettingsService.updateConsoleSettings(input.payload, {
|
|
134
|
+
context
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: actionIds.workspaceSettingsRead,
|
|
140
|
+
version: 1,
|
|
141
|
+
kind: "query",
|
|
142
|
+
channels: ["api", "automation", "internal"],
|
|
143
|
+
surfacesFrom: "workspace",
|
|
144
|
+
permission: {
|
|
145
|
+
require: "any",
|
|
146
|
+
permissions: ["workspace.settings.view", "workspace.settings.update"]
|
|
147
|
+
},
|
|
148
|
+
inputValidator: workspaceSlugParamsValidator,
|
|
149
|
+
outputValidator: assistantWorkspaceSettingsResource.operations.view.outputValidator,
|
|
150
|
+
idempotency: "none",
|
|
151
|
+
audit: {
|
|
152
|
+
actionName: actionIds.workspaceSettingsRead
|
|
153
|
+
},
|
|
154
|
+
observability: {},
|
|
155
|
+
async execute(input, context, deps) {
|
|
156
|
+
return deps.assistantSettingsService.getWorkspaceSettings(resolveWorkspace(context, input), {
|
|
157
|
+
context
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: actionIds.workspaceSettingsUpdate,
|
|
163
|
+
version: 1,
|
|
164
|
+
kind: "command",
|
|
165
|
+
channels: ["api", "automation", "internal"],
|
|
166
|
+
surfacesFrom: "workspace",
|
|
167
|
+
permission: {
|
|
168
|
+
require: "all",
|
|
169
|
+
permissions: ["workspace.settings.update"]
|
|
170
|
+
},
|
|
171
|
+
inputValidator: [
|
|
172
|
+
workspaceSlugParamsValidator,
|
|
173
|
+
{
|
|
174
|
+
patch: assistantWorkspaceSettingsResource.operations.patch.bodyValidator
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
outputValidator: assistantWorkspaceSettingsResource.operations.patch.outputValidator,
|
|
178
|
+
idempotency: "optional",
|
|
179
|
+
audit: {
|
|
180
|
+
actionName: actionIds.workspaceSettingsUpdate
|
|
181
|
+
},
|
|
182
|
+
observability: {},
|
|
183
|
+
async execute(input, context, deps) {
|
|
184
|
+
return deps.assistantSettingsService.updateWorkspaceSettings(resolveWorkspace(context, input), input.patch, {
|
|
185
|
+
context
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
export { assistantActions };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const ASSISTANT_CONVERSATIONS_REPOSITORY_TOKEN = "assistant.conversation.repository";
|
|
2
|
+
const ASSISTANT_MESSAGES_REPOSITORY_TOKEN = "assistant.message.repository";
|
|
3
|
+
const ASSISTANT_SETTINGS_REPOSITORY_TOKEN = "assistant.settings.repository";
|
|
4
|
+
const ASSISTANT_TRANSCRIPT_SERVICE_TOKEN = "assistant.transcript.service";
|
|
5
|
+
const ASSISTANT_CHAT_SERVICE_TOKEN = "assistant.chat.service";
|
|
6
|
+
const ASSISTANT_SETTINGS_SERVICE_TOKEN = "assistant.settings.service";
|
|
7
|
+
const ASSISTANT_AI_CLIENT_TOKEN = "assistant.ai.client";
|
|
8
|
+
const ASSISTANT_SERVICE_TOOL_CATALOG_TOKEN = "assistant.service.tool-catalog";
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
ASSISTANT_CONVERSATIONS_REPOSITORY_TOKEN,
|
|
12
|
+
ASSISTANT_MESSAGES_REPOSITORY_TOKEN,
|
|
13
|
+
ASSISTANT_SETTINGS_REPOSITORY_TOKEN,
|
|
14
|
+
ASSISTANT_TRANSCRIPT_SERVICE_TOKEN,
|
|
15
|
+
ASSISTANT_CHAT_SERVICE_TOKEN,
|
|
16
|
+
ASSISTANT_SETTINGS_SERVICE_TOKEN,
|
|
17
|
+
ASSISTANT_AI_CLIENT_TOKEN,
|
|
18
|
+
ASSISTANT_SERVICE_TOOL_CATALOG_TOKEN
|
|
19
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createOpenAiClient } from "./providers/openAiClient.js";
|
|
2
|
+
import { createDeepSeekClient } from "./providers/deepSeekClient.js";
|
|
3
|
+
import { createAnthropicClient } from "./providers/anthropicClient.js";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_AI_PROVIDER,
|
|
6
|
+
SUPPORTED_AI_PROVIDERS,
|
|
7
|
+
normalizeModel,
|
|
8
|
+
normalizeProvider,
|
|
9
|
+
normalizeTimeoutMs
|
|
10
|
+
} from "./providers/common.js";
|
|
11
|
+
|
|
12
|
+
function createAiClient(options = {}) {
|
|
13
|
+
const provider = normalizeProvider(options.provider || DEFAULT_AI_PROVIDER);
|
|
14
|
+
const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
|
|
15
|
+
const commonOptions = {
|
|
16
|
+
...options,
|
|
17
|
+
provider,
|
|
18
|
+
timeoutMs,
|
|
19
|
+
model: normalizeModel(options.model)
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (provider === "openai") {
|
|
23
|
+
return createOpenAiClient(commonOptions);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (provider === "deepseek") {
|
|
27
|
+
return createDeepSeekClient(commonOptions);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (provider === "anthropic") {
|
|
31
|
+
return createAnthropicClient(commonOptions);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new TypeError(
|
|
35
|
+
`Unsupported assistant provider: ${provider}. Supported providers: ${SUPPORTED_AI_PROVIDERS.join(", ")}.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
createAiClient,
|
|
41
|
+
SUPPORTED_AI_PROVIDERS,
|
|
42
|
+
DEFAULT_AI_PROVIDER
|
|
43
|
+
};
|