@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.
Files changed (57) hide show
  1. package/package.descriptor.mjs +284 -0
  2. package/package.json +31 -0
  3. package/src/client/components/AssistantClientElement.vue +1316 -0
  4. package/src/client/components/AssistantConsoleSettingsClientElement.vue +71 -0
  5. package/src/client/components/AssistantSettingsFormCard.vue +76 -0
  6. package/src/client/components/AssistantWorkspaceClientElement.vue +15 -0
  7. package/src/client/components/AssistantWorkspaceSettingsClientElement.vue +73 -0
  8. package/src/client/composables/useAssistantWorkspaceRuntime.js +789 -0
  9. package/src/client/index.js +12 -0
  10. package/src/client/lib/assistantApi.js +137 -0
  11. package/src/client/lib/assistantHttpClient.js +10 -0
  12. package/src/client/lib/markdownRenderer.js +31 -0
  13. package/src/client/providers/AssistantWebClientProvider.js +25 -0
  14. package/src/server/AssistantServiceProvider.js +179 -0
  15. package/src/server/actionIds.js +11 -0
  16. package/src/server/actions.js +191 -0
  17. package/src/server/diTokens.js +19 -0
  18. package/src/server/lib/aiClient.js +43 -0
  19. package/src/server/lib/ndjson.js +47 -0
  20. package/src/server/lib/providers/anthropicClient.js +375 -0
  21. package/src/server/lib/providers/common.js +158 -0
  22. package/src/server/lib/providers/deepSeekClient.js +22 -0
  23. package/src/server/lib/providers/openAiClient.js +13 -0
  24. package/src/server/lib/providers/openAiCompatibleClient.js +69 -0
  25. package/src/server/lib/resolveWorkspaceSlug.js +24 -0
  26. package/src/server/lib/serviceToolCatalog.js +459 -0
  27. package/src/server/registerRoutes.js +384 -0
  28. package/src/server/repositories/assistantSettingsRepository.js +100 -0
  29. package/src/server/repositories/conversationsRepository.js +244 -0
  30. package/src/server/repositories/messagesRepository.js +154 -0
  31. package/src/server/repositories/repositoryPersistenceUtils.js +63 -0
  32. package/src/server/services/assistantSettingsService.js +153 -0
  33. package/src/server/services/chatService.js +987 -0
  34. package/src/server/services/transcriptService.js +334 -0
  35. package/src/shared/assistantPaths.js +50 -0
  36. package/src/shared/assistantResource.js +323 -0
  37. package/src/shared/assistantSettingsResource.js +214 -0
  38. package/src/shared/index.js +39 -0
  39. package/src/shared/queryKeys.js +69 -0
  40. package/src/shared/settingsEvents.js +7 -0
  41. package/src/shared/streamEvents.js +31 -0
  42. package/src/shared/support/positiveInteger.js +9 -0
  43. package/templates/migrations/assistant_settings_initial.cjs +39 -0
  44. package/templates/migrations/assistant_transcripts_initial.cjs +51 -0
  45. package/templates/src/pages/admin/workspace/assistant/index.vue +7 -0
  46. package/test/aiConfigValidation.test.js +15 -0
  47. package/test/assistantApiSurfaceHeader.test.js +64 -0
  48. package/test/assistantResource.test.js +53 -0
  49. package/test/assistantSettingsResource.test.js +48 -0
  50. package/test/assistantSettingsService.test.js +133 -0
  51. package/test/chatService.test.js +841 -0
  52. package/test/descriptorSurfaceOption.test.js +35 -0
  53. package/test/queryKeys.test.js +41 -0
  54. package/test/resolveWorkspaceSlug.test.js +83 -0
  55. package/test/routeInputContracts.test.js +287 -0
  56. package/test/serviceToolCatalog.test.js +1235 -0
  57. package/test/transcriptService.test.js +175 -0
@@ -0,0 +1,244 @@
1
+ import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import {
4
+ parseJsonObject,
5
+ stringifyJsonObject,
6
+ toIso,
7
+ resolveInsertedId
8
+ } from "./repositoryPersistenceUtils.js";
9
+
10
+ function mapConversationRow(row = {}) {
11
+ return {
12
+ id: Number(row.id),
13
+ workspaceId: Number(row.workspace_id),
14
+ workspaceSlug: String(row.workspace_slug || ""),
15
+ workspaceName: String(row.workspace_name || ""),
16
+ title: String(row.title || "New conversation"),
17
+ createdByUserId: row.created_by_user_id == null ? null : Number(row.created_by_user_id),
18
+ createdByUserDisplayName: String(row.created_by_user_display_name || ""),
19
+ createdByUserEmail: String(row.created_by_user_email || ""),
20
+ status: String(row.status || "active"),
21
+ provider: String(row.provider || ""),
22
+ model: String(row.model || ""),
23
+ surfaceId: String(row.surface_id || ""),
24
+ startedAt: toIso(row.started_at),
25
+ endedAt: row.ended_at ? toIso(row.ended_at) : null,
26
+ messageCount: Number(row.message_count || 0),
27
+ metadata: parseJsonObject(row.metadata_json),
28
+ createdAt: toIso(row.created_at),
29
+ updatedAt: toIso(row.updated_at)
30
+ };
31
+ }
32
+
33
+ function normalizeCursorPagination(pagination = {}, { defaultLimit = 20, maxLimit = 200 } = {}) {
34
+ const cursor = parsePositiveInteger(pagination.cursor) || 0;
35
+ const limit = Math.max(1, Math.min(maxLimit, parsePositiveInteger(pagination.limit) || defaultLimit));
36
+
37
+ return {
38
+ cursor,
39
+ limit
40
+ };
41
+ }
42
+
43
+ function createConversationBaseQuery(client) {
44
+ return client("ai_conversations as c")
45
+ .leftJoin("workspaces as w", "w.id", "c.workspace_id")
46
+ .leftJoin("user_profiles as u", "u.id", "c.created_by_user_id")
47
+ .select(
48
+ "c.*",
49
+ client.raw("COALESCE(w.slug, '') AS workspace_slug"),
50
+ client.raw("COALESCE(w.name, '') AS workspace_name"),
51
+ client.raw("COALESCE(u.display_name, '') AS created_by_user_display_name"),
52
+ client.raw("COALESCE(u.email, '') AS created_by_user_email")
53
+ );
54
+ }
55
+
56
+ function createConversationsRepository(knex) {
57
+ if (!knex || typeof knex !== "function") {
58
+ throw new Error("createConversationsRepository requires knex client.");
59
+ }
60
+
61
+ async function findById(conversationId, options = {}) {
62
+ const numericConversationId = parsePositiveInteger(conversationId);
63
+ if (!numericConversationId) {
64
+ return null;
65
+ }
66
+
67
+ const client = options?.trx || knex;
68
+ const row = await createConversationBaseQuery(client)
69
+ .where("c.id", numericConversationId)
70
+ .first();
71
+
72
+ return row ? mapConversationRow(row) : null;
73
+ }
74
+
75
+ async function findByIdForWorkspaceAndUser(conversationId, workspaceId, actorUserId, options = {}) {
76
+ const numericConversationId = parsePositiveInteger(conversationId);
77
+ const numericWorkspaceId = parsePositiveInteger(workspaceId);
78
+ const numericActorUserId = parsePositiveInteger(actorUserId);
79
+ if (!numericConversationId || !numericWorkspaceId || !numericActorUserId) {
80
+ return null;
81
+ }
82
+
83
+ const client = options?.trx || knex;
84
+ const row = await createConversationBaseQuery(client)
85
+ .where("c.id", numericConversationId)
86
+ .where("c.workspace_id", numericWorkspaceId)
87
+ .where("c.created_by_user_id", numericActorUserId)
88
+ .first();
89
+
90
+ return row ? mapConversationRow(row) : null;
91
+ }
92
+
93
+ async function create(payload = {}, options = {}) {
94
+ const client = options?.trx || knex;
95
+ const now = new Date();
96
+ const insertResult = await client("ai_conversations").insert({
97
+ workspace_id: parsePositiveInteger(payload.workspaceId),
98
+ created_by_user_id: parsePositiveInteger(payload.createdByUserId) || null,
99
+ title: normalizeText(payload.title) || "New conversation",
100
+ status: normalizeText(payload.status).toLowerCase() || "active",
101
+ provider: normalizeText(payload.provider),
102
+ model: normalizeText(payload.model),
103
+ surface_id: normalizeText(payload.surfaceId).toLowerCase() || "admin",
104
+ message_count: parsePositiveInteger(payload.messageCount) || 0,
105
+ metadata_json: stringifyJsonObject(payload.metadata),
106
+ started_at: payload.startedAt ? new Date(payload.startedAt) : now,
107
+ ended_at: payload.endedAt ? new Date(payload.endedAt) : null,
108
+ created_at: now,
109
+ updated_at: now
110
+ });
111
+ const id = resolveInsertedId(insertResult);
112
+ if (!id) {
113
+ throw new Error("conversationsRepository.create could not resolve inserted id.");
114
+ }
115
+
116
+ return findById(id, {
117
+ trx: client
118
+ });
119
+ }
120
+
121
+ async function updateById(conversationId, patch = {}, options = {}) {
122
+ const numericConversationId = parsePositiveInteger(conversationId);
123
+ if (!numericConversationId) {
124
+ return null;
125
+ }
126
+
127
+ const client = options?.trx || knex;
128
+ const updatePatch = {};
129
+
130
+ if (Object.hasOwn(patch, "title")) {
131
+ updatePatch.title = normalizeText(patch.title) || "New conversation";
132
+ }
133
+ if (Object.hasOwn(patch, "status")) {
134
+ updatePatch.status = normalizeText(patch.status).toLowerCase() || "active";
135
+ }
136
+ if (Object.hasOwn(patch, "provider")) {
137
+ updatePatch.provider = normalizeText(patch.provider);
138
+ }
139
+ if (Object.hasOwn(patch, "model")) {
140
+ updatePatch.model = normalizeText(patch.model);
141
+ }
142
+ if (Object.hasOwn(patch, "surfaceId")) {
143
+ updatePatch.surface_id = normalizeText(patch.surfaceId).toLowerCase() || "admin";
144
+ }
145
+ if (Object.hasOwn(patch, "messageCount")) {
146
+ updatePatch.message_count = Math.max(0, Number(patch.messageCount || 0));
147
+ }
148
+ if (Object.hasOwn(patch, "endedAt")) {
149
+ updatePatch.ended_at = patch.endedAt ? new Date(patch.endedAt) : null;
150
+ }
151
+ if (Object.hasOwn(patch, "metadata")) {
152
+ updatePatch.metadata_json = stringifyJsonObject(patch.metadata);
153
+ }
154
+
155
+ if (Object.keys(updatePatch).length > 0) {
156
+ updatePatch.updated_at = new Date();
157
+ await client("ai_conversations").where({ id: numericConversationId }).update(updatePatch);
158
+ }
159
+
160
+ return findById(numericConversationId, {
161
+ trx: client
162
+ });
163
+ }
164
+
165
+ async function incrementMessageCount(conversationId, delta = 1, options = {}) {
166
+ const numericConversationId = parsePositiveInteger(conversationId);
167
+ if (!numericConversationId) {
168
+ return null;
169
+ }
170
+
171
+ const client = options?.trx || knex;
172
+ const incrementBy = Number.isInteger(Number(delta)) ? Number(delta) : 1;
173
+ await client("ai_conversations")
174
+ .where({ id: numericConversationId })
175
+ .update({
176
+ message_count: client.raw("GREATEST(0, message_count + ?)", [incrementBy]),
177
+ updated_at: new Date()
178
+ });
179
+
180
+ return findById(numericConversationId, {
181
+ trx: client
182
+ });
183
+ }
184
+
185
+ async function listForWorkspaceAndUser(workspaceId, actorUserId, pagination = {}, filters = {}, options = {}) {
186
+ const numericWorkspaceId = parsePositiveInteger(workspaceId);
187
+ const numericActorUserId = parsePositiveInteger(actorUserId);
188
+ if (!numericWorkspaceId || !numericActorUserId) {
189
+ return {
190
+ items: [],
191
+ nextCursor: null
192
+ };
193
+ }
194
+
195
+ const client = options?.trx || knex;
196
+ const { cursor, limit } = normalizeCursorPagination(pagination);
197
+
198
+ let query = createConversationBaseQuery(client)
199
+ .where("c.workspace_id", numericWorkspaceId)
200
+ .where("c.created_by_user_id", numericActorUserId);
201
+
202
+ const normalizedStatus = normalizeText(filters.status).toLowerCase();
203
+ if (normalizedStatus) {
204
+ query = query.where("c.status", normalizedStatus);
205
+ }
206
+ if (cursor > 0) {
207
+ query = query.where("c.id", "<", cursor);
208
+ }
209
+
210
+ const rows = await query
211
+ .orderBy("c.id", "desc")
212
+ .limit(limit + 1);
213
+
214
+ const hasMore = rows.length > limit;
215
+ const pageRows = hasMore ? rows.slice(0, limit) : rows;
216
+ const items = pageRows.map(mapConversationRow);
217
+ const nextCursor = hasMore && pageRows.length > 0 ? String(pageRows[pageRows.length - 1].id) : null;
218
+
219
+ return {
220
+ items,
221
+ nextCursor
222
+ };
223
+ }
224
+
225
+ async function transaction(callback) {
226
+ if (typeof knex.transaction !== "function") {
227
+ return callback(knex);
228
+ }
229
+
230
+ return knex.transaction(callback);
231
+ }
232
+
233
+ return Object.freeze({
234
+ findById,
235
+ findByIdForWorkspaceAndUser,
236
+ create,
237
+ updateById,
238
+ incrementMessageCount,
239
+ listForWorkspaceAndUser,
240
+ transaction
241
+ });
242
+ }
243
+
244
+ export { createConversationsRepository as createRepository, createConversationsRepository };
@@ -0,0 +1,154 @@
1
+ import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import {
4
+ parseJsonObject,
5
+ stringifyJsonObject,
6
+ toIso,
7
+ resolveInsertedId
8
+ } from "./repositoryPersistenceUtils.js";
9
+
10
+ function mapMessageRow(row = {}) {
11
+ return {
12
+ id: Number(row.id),
13
+ conversationId: Number(row.conversation_id),
14
+ workspaceId: Number(row.workspace_id),
15
+ seq: Number(row.seq),
16
+ role: String(row.role || ""),
17
+ kind: String(row.kind || "chat"),
18
+ clientMessageId: String(row.client_message_id || ""),
19
+ actorUserId: row.actor_user_id == null ? null : Number(row.actor_user_id),
20
+ contentText: row.content_text == null ? null : String(row.content_text),
21
+ metadata: parseJsonObject(row.metadata_json),
22
+ createdAt: toIso(row.created_at)
23
+ };
24
+ }
25
+
26
+ function normalizePagination(pagination = {}, { defaultPage = 1, defaultPageSize = 200, maxPageSize = 500 } = {}) {
27
+ const page = Math.max(1, parsePositiveInteger(pagination.page) || defaultPage);
28
+ const pageSize = Math.max(1, Math.min(maxPageSize, parsePositiveInteger(pagination.pageSize) || defaultPageSize));
29
+
30
+ return {
31
+ page,
32
+ pageSize
33
+ };
34
+ }
35
+
36
+ async function resolveNextSequence(client, conversationId) {
37
+ const row = await client("ai_messages").where({ conversation_id: conversationId }).max({ maxSeq: "seq" }).first();
38
+ const maxSeq = Number(row?.maxSeq || 0);
39
+ if (!Number.isInteger(maxSeq) || maxSeq < 0) {
40
+ return 1;
41
+ }
42
+
43
+ return maxSeq + 1;
44
+ }
45
+
46
+ function createMessagesRepository(knex) {
47
+ if (!knex || typeof knex !== "function") {
48
+ throw new Error("createMessagesRepository requires knex client.");
49
+ }
50
+
51
+ async function findById(messageId, options = {}) {
52
+ const numericMessageId = parsePositiveInteger(messageId);
53
+ if (!numericMessageId) {
54
+ return null;
55
+ }
56
+
57
+ const client = options?.trx || knex;
58
+ const row = await client("ai_messages").where({ id: numericMessageId }).first();
59
+ return row ? mapMessageRow(row) : null;
60
+ }
61
+
62
+ async function create(payload = {}, options = {}) {
63
+ const client = options?.trx || knex;
64
+ const conversationId = parsePositiveInteger(payload.conversationId);
65
+ const workspaceId = parsePositiveInteger(payload.workspaceId);
66
+ if (!conversationId || !workspaceId) {
67
+ throw new TypeError("messagesRepository.create requires conversationId and workspaceId.");
68
+ }
69
+
70
+ const seq = parsePositiveInteger(payload.seq) || (await resolveNextSequence(client, conversationId));
71
+ const insertResult = await client("ai_messages").insert({
72
+ conversation_id: conversationId,
73
+ workspace_id: workspaceId,
74
+ seq,
75
+ role: normalizeText(payload.role).toLowerCase(),
76
+ kind: normalizeText(payload.kind).toLowerCase() || "chat",
77
+ client_message_id: normalizeText(payload.clientMessageId),
78
+ actor_user_id: parsePositiveInteger(payload.actorUserId) || null,
79
+ content_text: payload.contentText == null ? null : String(payload.contentText),
80
+ metadata_json: stringifyJsonObject(payload.metadata),
81
+ created_at: payload.createdAt ? new Date(payload.createdAt) : new Date()
82
+ });
83
+ const id = resolveInsertedId(insertResult);
84
+ if (!id) {
85
+ throw new Error("messagesRepository.create could not resolve inserted id.");
86
+ }
87
+
88
+ return findById(id, {
89
+ trx: client
90
+ });
91
+ }
92
+
93
+ async function countByConversationForWorkspace(conversationId, workspaceId, options = {}) {
94
+ const numericConversationId = parsePositiveInteger(conversationId);
95
+ const numericWorkspaceId = parsePositiveInteger(workspaceId);
96
+ if (!numericConversationId || !numericWorkspaceId) {
97
+ return 0;
98
+ }
99
+
100
+ const client = options?.trx || knex;
101
+ const row = await client("ai_messages")
102
+ .where({
103
+ conversation_id: numericConversationId,
104
+ workspace_id: numericWorkspaceId
105
+ })
106
+ .count({ total: "*" })
107
+ .first();
108
+
109
+ const total = Number(row?.total || 0);
110
+ return Number.isFinite(total) && total > 0 ? total : 0;
111
+ }
112
+
113
+ async function listByConversationForWorkspace(conversationId, workspaceId, pagination = {}, options = {}) {
114
+ const numericConversationId = parsePositiveInteger(conversationId);
115
+ const numericWorkspaceId = parsePositiveInteger(workspaceId);
116
+ if (!numericConversationId || !numericWorkspaceId) {
117
+ return [];
118
+ }
119
+
120
+ const client = options?.trx || knex;
121
+ const { page, pageSize } = normalizePagination(pagination);
122
+ const offset = (page - 1) * pageSize;
123
+
124
+ const rows = await client("ai_messages")
125
+ .where({
126
+ conversation_id: numericConversationId,
127
+ workspace_id: numericWorkspaceId
128
+ })
129
+ .orderBy("seq", "asc")
130
+ .orderBy("id", "asc")
131
+ .limit(pageSize)
132
+ .offset(offset);
133
+
134
+ return rows.map(mapMessageRow);
135
+ }
136
+
137
+ async function transaction(callback) {
138
+ if (typeof knex.transaction !== "function") {
139
+ return callback(knex);
140
+ }
141
+
142
+ return knex.transaction(callback);
143
+ }
144
+
145
+ return Object.freeze({
146
+ findById,
147
+ create,
148
+ countByConversationForWorkspace,
149
+ listByConversationForWorkspace,
150
+ transaction
151
+ });
152
+ }
153
+
154
+ export { createMessagesRepository as createRepository, createMessagesRepository };
@@ -0,0 +1,63 @@
1
+ function parseJsonObject(value) {
2
+ if (value == null) {
3
+ return {};
4
+ }
5
+
6
+ try {
7
+ const parsed = typeof value === "string" ? JSON.parse(value) : value;
8
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9
+ return {};
10
+ }
11
+
12
+ return parsed;
13
+ } catch {
14
+ return {};
15
+ }
16
+ }
17
+
18
+ function stringifyJsonObject(value) {
19
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
20
+ return null;
21
+ }
22
+
23
+ return JSON.stringify(value);
24
+ }
25
+
26
+ function toIso(value) {
27
+ if (!value) {
28
+ return "";
29
+ }
30
+
31
+ const date = value instanceof Date ? value : new Date(value);
32
+ if (Number.isNaN(date.getTime())) {
33
+ return "";
34
+ }
35
+
36
+ return date.toISOString();
37
+ }
38
+
39
+ function resolveInsertedId(insertResult) {
40
+ if (Array.isArray(insertResult) && insertResult.length > 0) {
41
+ const first = insertResult[0];
42
+ if (first && typeof first === "object" && !Array.isArray(first)) {
43
+ const objectId = Number(first.id);
44
+ if (Number.isInteger(objectId) && objectId > 0) {
45
+ return objectId;
46
+ }
47
+ }
48
+
49
+ const scalarId = Number(first);
50
+ if (Number.isInteger(scalarId) && scalarId > 0) {
51
+ return scalarId;
52
+ }
53
+ }
54
+
55
+ const directId = Number(insertResult);
56
+ if (Number.isInteger(directId) && directId > 0) {
57
+ return directId;
58
+ }
59
+
60
+ return 0;
61
+ }
62
+
63
+ export { parseJsonObject, stringifyJsonObject, toIso, resolveInsertedId };
@@ -0,0 +1,153 @@
1
+ import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
+ import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators";
3
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
4
+ import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
5
+ import {
6
+ ASSISTANT_CONSOLE_SETTINGS_CHANGED_EVENT,
7
+ ASSISTANT_WORKSPACE_SETTINGS_CHANGED_EVENT
8
+ } from "../../shared/settingsEvents.js";
9
+
10
+ const serviceEvents = Object.freeze({
11
+ updateConsoleSettings: Object.freeze([
12
+ Object.freeze({
13
+ type: "entity.changed",
14
+ source: "assistant",
15
+ entity: "console.settings",
16
+ operation: "updated",
17
+ entityId: 1,
18
+ realtime: Object.freeze({
19
+ event: ASSISTANT_CONSOLE_SETTINGS_CHANGED_EVENT,
20
+ audience: "all_users"
21
+ })
22
+ })
23
+ ]),
24
+ updateWorkspaceSettings: Object.freeze([
25
+ Object.freeze({
26
+ type: "entity.changed",
27
+ source: "assistant",
28
+ entity: "workspace.settings",
29
+ operation: "updated",
30
+ entityId: ({ args }) => args?.[0]?.id || null,
31
+ realtime: Object.freeze({
32
+ event: ASSISTANT_WORKSPACE_SETTINGS_CHANGED_EVENT,
33
+ audience: "event_scope",
34
+ payload: ({ args }) => ({
35
+ workspaceSlug: String(args?.[0]?.slug || "").trim()
36
+ })
37
+ })
38
+ })
39
+ ])
40
+ });
41
+
42
+ function normalizeSurface(value) {
43
+ return normalizeText(value).toLowerCase();
44
+ }
45
+
46
+ function mapConsoleResponse(record = {}) {
47
+ return {
48
+ settings: {
49
+ workspaceSurfacePrompt: String(record.workspaceSurfacePrompt || "")
50
+ }
51
+ };
52
+ }
53
+
54
+ function mapWorkspaceResponse(record = {}) {
55
+ return {
56
+ settings: {
57
+ appSurfacePrompt: String(record.appSurfacePrompt || "")
58
+ }
59
+ };
60
+ }
61
+
62
+ function createService({ assistantSettingsRepository, consoleService } = {}) {
63
+ if (!assistantSettingsRepository || typeof assistantSettingsRepository.ensureConsoleSettings !== "function") {
64
+ throw new Error("assistantSettingsService requires assistantSettingsRepository.ensureConsoleSettings().");
65
+ }
66
+ if (!assistantSettingsRepository || typeof assistantSettingsRepository.ensureWorkspaceSettings !== "function") {
67
+ throw new Error("assistantSettingsService requires assistantSettingsRepository.ensureWorkspaceSettings().");
68
+ }
69
+ if (!assistantSettingsRepository || typeof assistantSettingsRepository.updateConsoleSettings !== "function") {
70
+ throw new Error("assistantSettingsService requires assistantSettingsRepository.updateConsoleSettings().");
71
+ }
72
+ if (!assistantSettingsRepository || typeof assistantSettingsRepository.updateWorkspaceSettings !== "function") {
73
+ throw new Error("assistantSettingsService requires assistantSettingsRepository.updateWorkspaceSettings().");
74
+ }
75
+ if (!consoleService || typeof consoleService.requireConsoleOwner !== "function") {
76
+ throw new Error("assistantSettingsService requires consoleService.requireConsoleOwner().");
77
+ }
78
+
79
+ async function getConsoleSettings(options = {}) {
80
+ await consoleService.requireConsoleOwner(options?.context, options);
81
+ const settings = await assistantSettingsRepository.ensureConsoleSettings(options);
82
+ return mapConsoleResponse(settings);
83
+ }
84
+
85
+ async function updateConsoleSettings(payload = {}, options = {}) {
86
+ await consoleService.requireConsoleOwner(options?.context, options);
87
+ const source = normalizeObjectInput(payload);
88
+ const patch = pickOwnProperties(source, ["workspaceSurfacePrompt"]);
89
+ if (Object.keys(patch).length < 1) {
90
+ const settings = await assistantSettingsRepository.ensureConsoleSettings(options);
91
+ return mapConsoleResponse(settings);
92
+ }
93
+ const settings = await assistantSettingsRepository.updateConsoleSettings({
94
+ workspaceSurfacePrompt: String(patch.workspaceSurfacePrompt || "")
95
+ }, options);
96
+ return mapConsoleResponse(settings);
97
+ }
98
+
99
+ async function getWorkspaceSettings(workspace, options = {}) {
100
+ const workspaceId = parsePositiveInteger(workspace?.id);
101
+ if (!workspaceId) {
102
+ throw new Error("assistantSettingsService.getWorkspaceSettings requires workspace.id.");
103
+ }
104
+
105
+ const settings = await assistantSettingsRepository.ensureWorkspaceSettings(workspaceId, options);
106
+ return mapWorkspaceResponse(settings);
107
+ }
108
+
109
+ async function updateWorkspaceSettings(workspace, payload = {}, options = {}) {
110
+ const workspaceId = parsePositiveInteger(workspace?.id);
111
+ if (!workspaceId) {
112
+ throw new Error("assistantSettingsService.updateWorkspaceSettings requires workspace.id.");
113
+ }
114
+
115
+ const source = normalizeObjectInput(payload);
116
+ const patch = pickOwnProperties(source, ["appSurfacePrompt"]);
117
+ if (Object.keys(patch).length < 1) {
118
+ const settings = await assistantSettingsRepository.ensureWorkspaceSettings(workspaceId, options);
119
+ return mapWorkspaceResponse(settings);
120
+ }
121
+ const settings = await assistantSettingsRepository.updateWorkspaceSettings(
122
+ workspaceId,
123
+ {
124
+ appSurfacePrompt: String(patch.appSurfacePrompt || "")
125
+ },
126
+ options
127
+ );
128
+
129
+ return mapWorkspaceResponse(settings);
130
+ }
131
+
132
+ async function resolveSystemPrompt(workspace, { surface = "" } = {}, options = {}) {
133
+ const normalizedSurface = normalizeSurface(surface);
134
+
135
+ if (normalizedSurface === "app") {
136
+ const workspaceSettings = await getWorkspaceSettings(workspace, options);
137
+ return String(workspaceSettings?.settings?.appSurfacePrompt || "");
138
+ }
139
+
140
+ const consoleSettings = await assistantSettingsRepository.ensureConsoleSettings(options);
141
+ return String(consoleSettings.workspaceSurfacePrompt || "");
142
+ }
143
+
144
+ return Object.freeze({
145
+ getConsoleSettings,
146
+ updateConsoleSettings,
147
+ getWorkspaceSettings,
148
+ updateWorkspaceSettings,
149
+ resolveSystemPrompt
150
+ });
151
+ }
152
+
153
+ export { createService, serviceEvents };