@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.
Files changed (76) hide show
  1. package/package.descriptor.mjs +346 -142
  2. package/package.json +3 -19
  3. package/src/server/buildTemplateContext.js +107 -0
  4. package/templates/migrations/assistant_config_initial.cjs +25 -0
  5. package/templates/migrations/assistant_transcripts_initial.cjs +21 -14
  6. package/templates/src/local-package/client/components/AssistantSettingsClientElement.vue +88 -0
  7. package/templates/src/local-package/client/components/AssistantSurfaceClientElement.vue +10 -0
  8. package/{src/client/composables/useAssistantWorkspaceRuntime.js → templates/src/local-package/client/composables/useAssistantRuntime.js} +91 -114
  9. package/templates/src/local-package/client/index.js +3 -0
  10. package/templates/src/local-package/client/providers/AssistantClientProvider.js +16 -0
  11. package/templates/src/local-package/package.descriptor.mjs +85 -0
  12. package/templates/src/local-package/package.json +11 -0
  13. package/{src/server/AssistantServiceProvider.js → templates/src/local-package/server/AssistantProvider.js} +37 -61
  14. package/templates/src/local-package/server/actionIds.js +9 -0
  15. package/templates/src/local-package/server/actions.js +190 -0
  16. package/templates/src/local-package/server/registerRoutes.js +296 -0
  17. package/templates/src/local-package/server/repositories/assistantConfigRepository.js +141 -0
  18. package/{src → templates/src/local-package}/server/repositories/conversationsRepository.js +44 -45
  19. package/{src → templates/src/local-package}/server/repositories/messagesRepository.js +49 -34
  20. package/templates/src/local-package/server/services/assistantConfigService.js +90 -0
  21. package/{src → templates/src/local-package}/server/services/chatService.js +45 -37
  22. package/{src → templates/src/local-package}/server/services/transcriptService.js +61 -82
  23. package/templates/src/local-package/shared/assistantRuntimeConfig.js +13 -0
  24. package/templates/src/local-package/shared/index.js +1 -0
  25. package/templates/src/pages/assistant/index.vue +7 -0
  26. package/test/buildTemplateContext.test.js +112 -0
  27. package/test/packageDescriptor.test.js +69 -0
  28. package/src/client/components/AssistantClientElement.vue +0 -1316
  29. package/src/client/components/AssistantConsoleSettingsClientElement.vue +0 -70
  30. package/src/client/components/AssistantSettingsFormCard.vue +0 -76
  31. package/src/client/components/AssistantWorkspaceClientElement.vue +0 -15
  32. package/src/client/components/AssistantWorkspaceSettingsClientElement.vue +0 -72
  33. package/src/client/index.js +0 -10
  34. package/src/client/lib/assistantApi.js +0 -137
  35. package/src/client/lib/assistantHttpClient.js +0 -10
  36. package/src/client/lib/markdownRenderer.js +0 -31
  37. package/src/client/providers/AssistantWebClientProvider.js +0 -20
  38. package/src/server/actionIds.js +0 -11
  39. package/src/server/actions.js +0 -191
  40. package/src/server/lib/aiClient.js +0 -43
  41. package/src/server/lib/ndjson.js +0 -47
  42. package/src/server/lib/providers/anthropicClient.js +0 -375
  43. package/src/server/lib/providers/common.js +0 -150
  44. package/src/server/lib/providers/deepSeekClient.js +0 -22
  45. package/src/server/lib/providers/openAiClient.js +0 -13
  46. package/src/server/lib/providers/openAiCompatibleClient.js +0 -69
  47. package/src/server/lib/resolveWorkspaceSlug.js +0 -24
  48. package/src/server/lib/serviceToolCatalog.js +0 -459
  49. package/src/server/registerRoutes.js +0 -383
  50. package/src/server/repositories/assistantSettingsRepository.js +0 -100
  51. package/src/server/repositories/repositoryPersistenceUtils.js +0 -48
  52. package/src/server/services/assistantSettingsService.js +0 -149
  53. package/src/shared/assistantPaths.js +0 -50
  54. package/src/shared/assistantResource.js +0 -317
  55. package/src/shared/assistantSettingsResource.js +0 -197
  56. package/src/shared/index.js +0 -43
  57. package/src/shared/queryKeys.js +0 -69
  58. package/src/shared/settingsEvents.js +0 -6
  59. package/src/shared/streamEvents.js +0 -29
  60. package/src/shared/support/conversationStatus.js +0 -18
  61. package/src/shared/support/jsonObject.js +0 -18
  62. package/src/shared/support/positiveInteger.js +0 -9
  63. package/templates/migrations/assistant_settings_initial.cjs +0 -37
  64. package/templates/src/pages/admin/workspace/assistant/index.vue +0 -7
  65. package/test/aiConfigValidation.test.js +0 -15
  66. package/test/assistantApiSurfaceHeader.test.js +0 -64
  67. package/test/assistantResource.test.js +0 -53
  68. package/test/assistantSettingsResource.test.js +0 -48
  69. package/test/assistantSettingsService.test.js +0 -133
  70. package/test/chatService.test.js +0 -841
  71. package/test/descriptorSurfaceOption.test.js +0 -35
  72. package/test/queryKeys.test.js +0 -41
  73. package/test/resolveWorkspaceSlug.test.js +0 -83
  74. package/test/routeInputContracts.test.js +0 -286
  75. package/test/serviceToolCatalog.test.js +0 -1235
  76. package/test/transcriptService.test.js +0 -175
@@ -0,0 +1,141 @@
1
+ import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { resolveInsertedId } from "@jskit-ai/assistant-core/server";
4
+ import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
5
+
6
+ function normalizeTargetSurfaceId(value = "") {
7
+ return normalizeText(value).toLowerCase() || assistantRuntimeConfig.runtimeSurfaceId;
8
+ }
9
+
10
+ function normalizeWorkspaceId(value) {
11
+ return parsePositiveInteger(value) || null;
12
+ }
13
+
14
+ function buildScopeKey(targetSurfaceId, workspaceId = null) {
15
+ const normalizedTargetSurfaceId = normalizeTargetSurfaceId(targetSurfaceId);
16
+ const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
17
+ if (normalizedWorkspaceId) {
18
+ return `${normalizedTargetSurfaceId}:workspace:${normalizedWorkspaceId}`;
19
+ }
20
+
21
+ return `${normalizedTargetSurfaceId}:global`;
22
+ }
23
+
24
+ function mapConfigRow(row = {}) {
25
+ return {
26
+ targetSurfaceId: normalizeTargetSurfaceId(row.target_surface_id),
27
+ scopeKey: normalizeText(row.scope_key),
28
+ workspaceId: normalizeWorkspaceId(row.workspace_id),
29
+ settings: {
30
+ systemPrompt: String(row.system_prompt || "")
31
+ }
32
+ };
33
+ }
34
+
35
+ function createDefaultRecord({ targetSurfaceId = "", workspaceId = null } = {}) {
36
+ const normalizedTargetSurfaceId = normalizeTargetSurfaceId(targetSurfaceId);
37
+ const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
38
+
39
+ return {
40
+ targetSurfaceId: normalizedTargetSurfaceId,
41
+ scopeKey: buildScopeKey(normalizedTargetSurfaceId, normalizedWorkspaceId),
42
+ workspaceId: normalizedWorkspaceId,
43
+ settings: {
44
+ systemPrompt: ""
45
+ }
46
+ };
47
+ }
48
+
49
+ function createRepository(knex) {
50
+ if (!knex || typeof knex !== "function") {
51
+ throw new Error("createAssistantConfigRepository requires knex client.");
52
+ }
53
+
54
+ async function findByScope({ targetSurfaceId = "", workspaceId = null } = {}, options = {}) {
55
+ const client = options?.trx || knex;
56
+ const defaultRecord = createDefaultRecord({
57
+ targetSurfaceId,
58
+ workspaceId
59
+ });
60
+ const row = await client(assistantRuntimeConfig.configTable)
61
+ .where({
62
+ target_surface_id: defaultRecord.targetSurfaceId,
63
+ scope_key: defaultRecord.scopeKey
64
+ })
65
+ .first();
66
+
67
+ return row ? mapConfigRow(row) : null;
68
+ }
69
+
70
+ async function upsertByScope({ targetSurfaceId = "", workspaceId = null, patch = {} } = {}, options = {}) {
71
+ const client = options?.trx || knex;
72
+ const defaultRecord = createDefaultRecord({
73
+ targetSurfaceId,
74
+ workspaceId
75
+ });
76
+ const existing = await findByScope(defaultRecord, {
77
+ trx: client
78
+ });
79
+ const nextSystemPrompt = Object.hasOwn(patch || {}, "systemPrompt")
80
+ ? String(patch.systemPrompt || "")
81
+ : String(existing?.settings?.systemPrompt || defaultRecord.settings.systemPrompt);
82
+ const now = new Date();
83
+
84
+ if (existing) {
85
+ await client(assistantRuntimeConfig.configTable)
86
+ .where({
87
+ target_surface_id: defaultRecord.targetSurfaceId,
88
+ scope_key: defaultRecord.scopeKey
89
+ })
90
+ .update({
91
+ workspace_id: defaultRecord.workspaceId,
92
+ system_prompt: nextSystemPrompt,
93
+ updated_at: now
94
+ });
95
+
96
+ return {
97
+ ...defaultRecord,
98
+ settings: {
99
+ systemPrompt: nextSystemPrompt
100
+ }
101
+ };
102
+ }
103
+
104
+ const insertResult = await client(assistantRuntimeConfig.configTable).insert({
105
+ target_surface_id: defaultRecord.targetSurfaceId,
106
+ scope_key: defaultRecord.scopeKey,
107
+ workspace_id: defaultRecord.workspaceId,
108
+ system_prompt: nextSystemPrompt,
109
+ created_at: now,
110
+ updated_at: now
111
+ });
112
+ const insertedId = resolveInsertedId(insertResult);
113
+ if (!insertedId) {
114
+ return {
115
+ ...defaultRecord,
116
+ settings: {
117
+ systemPrompt: nextSystemPrompt
118
+ }
119
+ };
120
+ }
121
+
122
+ const insertedRow = await client(assistantRuntimeConfig.configTable)
123
+ .where({ id: insertedId })
124
+ .first();
125
+
126
+ return insertedRow ? mapConfigRow(insertedRow) : {
127
+ ...defaultRecord,
128
+ settings: {
129
+ systemPrompt: nextSystemPrompt
130
+ }
131
+ };
132
+ }
133
+
134
+ return Object.freeze({
135
+ createDefaultRecord,
136
+ findByScope,
137
+ upsertByScope
138
+ });
139
+ }
140
+
141
+ export { createRepository };
@@ -1,27 +1,37 @@
1
+ import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
1
2
  import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
3
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
- import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
4
4
  import {
5
5
  parseJsonObject,
6
+ resolveInsertedId,
6
7
  stringifyJsonObject,
7
- toIso,
8
- resolveInsertedId
9
- } from "./repositoryPersistenceUtils.js";
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
+ }
10
24
 
11
25
  function mapConversationRow(row = {}) {
12
26
  return {
13
27
  id: Number(row.id),
14
- workspaceId: Number(row.workspace_id),
15
- workspaceSlug: String(row.workspace_slug || ""),
16
- workspaceName: String(row.workspace_name || ""),
28
+ workspaceId: normalizeWorkspaceId(row.workspace_id),
17
29
  title: String(row.title || "New conversation"),
18
30
  createdByUserId: row.created_by_user_id == null ? null : Number(row.created_by_user_id),
19
- createdByUserDisplayName: String(row.created_by_user_display_name || ""),
20
- createdByUserEmail: String(row.created_by_user_email || ""),
21
31
  status: String(row.status || "active"),
22
32
  provider: String(row.provider || ""),
23
33
  model: String(row.model || ""),
24
- surfaceSid: String(row.surface_sid || ""),
34
+ surfaceId: String(row.surface_id || ""),
25
35
  startedAt: toIso(row.started_at),
26
36
  endedAt: row.ended_at ? toIso(row.ended_at) : null,
27
37
  messageCount: Number(row.message_count || 0),
@@ -42,19 +52,10 @@ function normalizeCursorPagination(pagination = {}, { defaultLimit = 20, maxLimi
42
52
  }
43
53
 
44
54
  function createConversationBaseQuery(client) {
45
- return client("ai_conversations as c")
46
- .leftJoin("workspaces as w", "w.id", "c.workspace_id")
47
- .leftJoin("users as u", "u.id", "c.created_by_user_id")
48
- .select(
49
- "c.*",
50
- client.raw("COALESCE(w.slug, '') AS workspace_slug"),
51
- client.raw("COALESCE(w.name, '') AS workspace_name"),
52
- client.raw("COALESCE(u.display_name, '') AS created_by_user_display_name"),
53
- client.raw("COALESCE(u.email, '') AS created_by_user_email")
54
- );
55
+ return client(`${assistantRuntimeConfig.conversationsTable} as c`).select("c.*");
55
56
  }
56
57
 
57
- function createConversationsRepository(knex) {
58
+ function createRepository(knex) {
58
59
  if (!knex || typeof knex !== "function") {
59
60
  throw new Error("createConversationsRepository requires knex client.");
60
61
  }
@@ -73,20 +74,19 @@ function createConversationsRepository(knex) {
73
74
  return row ? mapConversationRow(row) : null;
74
75
  }
75
76
 
76
- async function findByIdForWorkspaceAndUser(conversationId, workspaceId, actorUserId, options = {}) {
77
+ async function findByIdForActorScope(conversationId, { workspaceId = null, actorUserId = null } = {}, options = {}) {
77
78
  const numericConversationId = parsePositiveInteger(conversationId);
78
- const numericWorkspaceId = parsePositiveInteger(workspaceId);
79
79
  const numericActorUserId = parsePositiveInteger(actorUserId);
80
- if (!numericConversationId || !numericWorkspaceId || !numericActorUserId) {
80
+ if (!numericConversationId || !numericActorUserId) {
81
81
  return null;
82
82
  }
83
83
 
84
84
  const client = options?.trx || knex;
85
- const row = await createConversationBaseQuery(client)
85
+ const query = createConversationBaseQuery(client)
86
86
  .where("c.id", numericConversationId)
87
- .where("c.workspace_id", numericWorkspaceId)
88
- .where("c.created_by_user_id", numericActorUserId)
89
- .first();
87
+ .where("c.created_by_user_id", numericActorUserId);
88
+ applyWorkspaceScope(query, "c.workspace_id", workspaceId);
89
+ const row = await query.first();
90
90
 
91
91
  return row ? mapConversationRow(row) : null;
92
92
  }
@@ -94,14 +94,14 @@ function createConversationsRepository(knex) {
94
94
  async function create(payload = {}, options = {}) {
95
95
  const client = options?.trx || knex;
96
96
  const now = new Date();
97
- const insertResult = await client("ai_conversations").insert({
98
- workspace_id: parsePositiveInteger(payload.workspaceId),
97
+ const insertResult = await client(assistantRuntimeConfig.conversationsTable).insert({
98
+ workspace_id: normalizeWorkspaceId(payload.workspaceId),
99
99
  created_by_user_id: parsePositiveInteger(payload.createdByUserId) || null,
100
100
  title: normalizeText(payload.title) || "New conversation",
101
101
  status: normalizeText(payload.status).toLowerCase() || "active",
102
102
  provider: normalizeText(payload.provider),
103
103
  model: normalizeText(payload.model),
104
- surface_sid: normalizeText(payload.surfaceSid).toLowerCase() || "admin",
104
+ surface_id: normalizeText(payload.surfaceId).toLowerCase() || assistantRuntimeConfig.runtimeSurfaceId,
105
105
  message_count: parsePositiveInteger(payload.messageCount) || 0,
106
106
  metadata_json: stringifyJsonObject(payload.metadata),
107
107
  started_at: payload.startedAt ? new Date(payload.startedAt) : now,
@@ -140,8 +140,8 @@ function createConversationsRepository(knex) {
140
140
  if (Object.hasOwn(patch, "model")) {
141
141
  updatePatch.model = normalizeText(patch.model);
142
142
  }
143
- if (Object.hasOwn(patch, "surfaceSid")) {
144
- updatePatch.surface_sid = normalizeText(patch.surfaceSid).toLowerCase() || "admin";
143
+ if (Object.hasOwn(patch, "surfaceId")) {
144
+ updatePatch.surface_id = normalizeText(patch.surfaceId).toLowerCase() || assistantRuntimeConfig.runtimeSurfaceId;
145
145
  }
146
146
  if (Object.hasOwn(patch, "messageCount")) {
147
147
  updatePatch.message_count = Math.max(0, Number(patch.messageCount || 0));
@@ -155,7 +155,9 @@ function createConversationsRepository(knex) {
155
155
 
156
156
  if (Object.keys(updatePatch).length > 0) {
157
157
  updatePatch.updated_at = new Date();
158
- await client("ai_conversations").where({ id: numericConversationId }).update(updatePatch);
158
+ await client(assistantRuntimeConfig.conversationsTable)
159
+ .where({ id: numericConversationId })
160
+ .update(updatePatch);
159
161
  }
160
162
 
161
163
  return findById(numericConversationId, {
@@ -171,7 +173,7 @@ function createConversationsRepository(knex) {
171
173
 
172
174
  const client = options?.trx || knex;
173
175
  const incrementBy = Number.isInteger(Number(delta)) ? Number(delta) : 1;
174
- await client("ai_conversations")
176
+ await client(assistantRuntimeConfig.conversationsTable)
175
177
  .where({ id: numericConversationId })
176
178
  .update({
177
179
  message_count: client.raw("GREATEST(0, message_count + ?)", [incrementBy]),
@@ -183,10 +185,9 @@ function createConversationsRepository(knex) {
183
185
  });
184
186
  }
185
187
 
186
- async function listForWorkspaceAndUser(workspaceId, actorUserId, pagination = {}, filters = {}, options = {}) {
187
- const numericWorkspaceId = parsePositiveInteger(workspaceId);
188
+ async function listForActorScope({ workspaceId = null, actorUserId = null, pagination = {}, filters = {} } = {}, options = {}) {
188
189
  const numericActorUserId = parsePositiveInteger(actorUserId);
189
- if (!numericWorkspaceId || !numericActorUserId) {
190
+ if (!numericActorUserId) {
190
191
  return {
191
192
  items: [],
192
193
  nextCursor: null
@@ -195,10 +196,8 @@ function createConversationsRepository(knex) {
195
196
 
196
197
  const client = options?.trx || knex;
197
198
  const { cursor, limit } = normalizeCursorPagination(pagination);
198
-
199
- let query = createConversationBaseQuery(client)
200
- .where("c.workspace_id", numericWorkspaceId)
201
- .where("c.created_by_user_id", numericActorUserId);
199
+ let query = createConversationBaseQuery(client).where("c.created_by_user_id", numericActorUserId);
200
+ query = applyWorkspaceScope(query, "c.workspace_id", workspaceId);
202
201
 
203
202
  const normalizedStatus = normalizeText(filters.status).toLowerCase();
204
203
  if (normalizedStatus) {
@@ -229,13 +228,13 @@ function createConversationsRepository(knex) {
229
228
 
230
229
  return Object.freeze({
231
230
  findById,
232
- findByIdForWorkspaceAndUser,
231
+ findByIdForActorScope,
233
232
  create,
234
233
  updateById,
235
234
  incrementMessageCount,
236
- listForWorkspaceAndUser,
235
+ listForActorScope,
237
236
  transaction
238
237
  });
239
238
  }
240
239
 
241
- export { createConversationsRepository as createRepository, createConversationsRepository };
240
+ export { createRepository };
@@ -1,18 +1,32 @@
1
+ import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
1
2
  import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
3
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
- import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
4
4
  import {
5
5
  parseJsonObject,
6
+ resolveInsertedId,
6
7
  stringifyJsonObject,
7
- toIso,
8
- resolveInsertedId
9
- } from "./repositoryPersistenceUtils.js";
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
+ }
10
24
 
11
25
  function mapMessageRow(row = {}) {
12
26
  return {
13
27
  id: Number(row.id),
14
28
  conversationId: Number(row.conversation_id),
15
- workspaceId: Number(row.workspace_id),
29
+ workspaceId: normalizeWorkspaceId(row.workspace_id),
16
30
  seq: Number(row.seq),
17
31
  role: String(row.role || ""),
18
32
  kind: String(row.kind || "chat"),
@@ -35,7 +49,10 @@ function normalizePagination(pagination = {}, { defaultPage = 1, defaultPageSize
35
49
  }
36
50
 
37
51
  async function resolveNextSequence(client, conversationId) {
38
- const row = await client("ai_messages").where({ conversation_id: conversationId }).max({ maxSeq: "seq" }).first();
52
+ const row = await client(assistantRuntimeConfig.messagesTable)
53
+ .where({ conversation_id: conversationId })
54
+ .max({ maxSeq: "seq" })
55
+ .first();
39
56
  const maxSeq = Number(row?.maxSeq || 0);
40
57
  if (!Number.isInteger(maxSeq) || maxSeq < 0) {
41
58
  return 1;
@@ -44,7 +61,7 @@ async function resolveNextSequence(client, conversationId) {
44
61
  return maxSeq + 1;
45
62
  }
46
63
 
47
- function createMessagesRepository(knex) {
64
+ function createRepository(knex) {
48
65
  if (!knex || typeof knex !== "function") {
49
66
  throw new Error("createMessagesRepository requires knex client.");
50
67
  }
@@ -56,22 +73,24 @@ function createMessagesRepository(knex) {
56
73
  }
57
74
 
58
75
  const client = options?.trx || knex;
59
- const row = await client("ai_messages").where({ id: numericMessageId }).first();
76
+ const row = await client(assistantRuntimeConfig.messagesTable)
77
+ .where({ id: numericMessageId })
78
+ .first();
79
+
60
80
  return row ? mapMessageRow(row) : null;
61
81
  }
62
82
 
63
83
  async function create(payload = {}, options = {}) {
64
84
  const client = options?.trx || knex;
65
85
  const conversationId = parsePositiveInteger(payload.conversationId);
66
- const workspaceId = parsePositiveInteger(payload.workspaceId);
67
- if (!conversationId || !workspaceId) {
68
- throw new TypeError("messagesRepository.create requires conversationId and workspaceId.");
86
+ if (!conversationId) {
87
+ throw new TypeError("messagesRepository.create requires conversationId.");
69
88
  }
70
89
 
71
90
  const seq = parsePositiveInteger(payload.seq) || (await resolveNextSequence(client, conversationId));
72
- const insertResult = await client("ai_messages").insert({
91
+ const insertResult = await client(assistantRuntimeConfig.messagesTable).insert({
73
92
  conversation_id: conversationId,
74
- workspace_id: workspaceId,
93
+ workspace_id: normalizeWorkspaceId(payload.workspaceId),
75
94
  seq,
76
95
  role: normalizeText(payload.role).toLowerCase(),
77
96
  kind: normalizeText(payload.kind).toLowerCase() || "chat",
@@ -91,30 +110,26 @@ function createMessagesRepository(knex) {
91
110
  });
92
111
  }
93
112
 
94
- async function countByConversationForWorkspace(conversationId, workspaceId, options = {}) {
113
+ async function countByConversationScope(conversationId, { workspaceId = null } = {}, options = {}) {
95
114
  const numericConversationId = parsePositiveInteger(conversationId);
96
- const numericWorkspaceId = parsePositiveInteger(workspaceId);
97
- if (!numericConversationId || !numericWorkspaceId) {
115
+ if (!numericConversationId) {
98
116
  return 0;
99
117
  }
100
118
 
101
119
  const client = options?.trx || knex;
102
- const row = await client("ai_messages")
103
- .where({
104
- conversation_id: numericConversationId,
105
- workspace_id: numericWorkspaceId
106
- })
107
- .count({ total: "*" })
108
- .first();
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();
109
125
 
110
126
  const total = Number(row?.total || 0);
111
127
  return Number.isFinite(total) && total > 0 ? total : 0;
112
128
  }
113
129
 
114
- async function listByConversationForWorkspace(conversationId, workspaceId, pagination = {}, options = {}) {
130
+ async function listByConversationScope(conversationId, { workspaceId = null } = {}, pagination = {}, options = {}) {
115
131
  const numericConversationId = parsePositiveInteger(conversationId);
116
- const numericWorkspaceId = parsePositiveInteger(workspaceId);
117
- if (!numericConversationId || !numericWorkspaceId) {
132
+ if (!numericConversationId) {
118
133
  return [];
119
134
  }
120
135
 
@@ -122,11 +137,11 @@ function createMessagesRepository(knex) {
122
137
  const { page, pageSize } = normalizePagination(pagination);
123
138
  const offset = (page - 1) * pageSize;
124
139
 
125
- const rows = await client("ai_messages")
126
- .where({
127
- conversation_id: numericConversationId,
128
- workspace_id: numericWorkspaceId
129
- })
140
+ const query = client(assistantRuntimeConfig.messagesTable).where({
141
+ conversation_id: numericConversationId
142
+ });
143
+ applyWorkspaceScope(query, "workspace_id", workspaceId);
144
+ const rows = await query
130
145
  .orderBy("seq", "asc")
131
146
  .orderBy("id", "asc")
132
147
  .limit(pageSize)
@@ -142,10 +157,10 @@ function createMessagesRepository(knex) {
142
157
  return Object.freeze({
143
158
  findById,
144
159
  create,
145
- countByConversationForWorkspace,
146
- listByConversationForWorkspace,
160
+ countByConversationScope,
161
+ listByConversationScope,
147
162
  transaction
148
163
  });
149
164
  }
150
165
 
151
- export { createMessagesRepository as createRepository, createMessagesRepository };
166
+ export { createRepository };
@@ -0,0 +1,90 @@
1
+ import { AppError, parsePositiveInteger } 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 { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
5
+
6
+ function createService({ assistantConfigRepository, consoleService = 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
+ if (
17
+ assistantRuntimeConfig.settingsSurfaceRequiresConsoleOwner &&
18
+ (!consoleService || typeof consoleService.requireConsoleOwner !== "function")
19
+ ) {
20
+ throw new Error("assistantConfigService requires consoleService.requireConsoleOwner() for console-owner settings surfaces.");
21
+ }
22
+
23
+ async function requireSettingsAccess(context = {}, options = {}) {
24
+ if (assistantRuntimeConfig.settingsSurfaceRequiresConsoleOwner !== true) {
25
+ return;
26
+ }
27
+
28
+ await consoleService.requireConsoleOwner(context, options);
29
+ }
30
+
31
+ function resolveConfigWorkspaceId(workspace = null, input = {}, context = {}) {
32
+ if (assistantRuntimeConfig.configScope !== "workspace") {
33
+ return null;
34
+ }
35
+
36
+ const resolvedWorkspace = workspace || resolveWorkspace(context, input);
37
+ const workspaceId = parsePositiveInteger(resolvedWorkspace?.id);
38
+ if (!workspaceId) {
39
+ throw new AppError(409, "Workspace selection required.");
40
+ }
41
+
42
+ return workspaceId;
43
+ }
44
+
45
+ async function getSettings(input = {}, options = {}) {
46
+ await requireSettingsAccess(options?.context, options);
47
+
48
+ const workspaceId = resolveConfigWorkspaceId(null, input, options?.context);
49
+ const existing = await assistantConfigRepository.findByScope({
50
+ targetSurfaceId: assistantRuntimeConfig.runtimeSurfaceId,
51
+ workspaceId
52
+ });
53
+
54
+ return existing || assistantConfigRepository.createDefaultRecord({
55
+ targetSurfaceId: assistantRuntimeConfig.runtimeSurfaceId,
56
+ workspaceId
57
+ });
58
+ }
59
+
60
+ async function updateSettings(input = {}, patch = {}, options = {}) {
61
+ await requireSettingsAccess(options?.context, options);
62
+
63
+ const workspaceId = resolveConfigWorkspaceId(null, input, options?.context);
64
+ const normalizedPatch = normalizeObject(patch);
65
+
66
+ return assistantConfigRepository.upsertByScope({
67
+ targetSurfaceId: assistantRuntimeConfig.runtimeSurfaceId,
68
+ workspaceId,
69
+ patch: normalizedPatch
70
+ });
71
+ }
72
+
73
+ async function resolveSystemPrompt(workspace = null, _options = {}, serviceOptions = {}) {
74
+ const workspaceId = resolveConfigWorkspaceId(workspace, serviceOptions?.input || {}, serviceOptions?.context);
75
+ const existing = await assistantConfigRepository.findByScope({
76
+ targetSurfaceId: assistantRuntimeConfig.runtimeSurfaceId,
77
+ workspaceId
78
+ });
79
+
80
+ return String(existing?.settings?.systemPrompt || "");
81
+ }
82
+
83
+ return Object.freeze({
84
+ getSettings,
85
+ updateSettings,
86
+ resolveSystemPrompt
87
+ });
88
+ }
89
+
90
+ export { createService };