@jskit-ai/assistant-runtime 0.1.1

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