@jskit-ai/assistant-runtime 0.1.2 → 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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/assistant-runtime",
4
- version: "0.1.2",
4
+ version: "0.1.4",
5
5
  kind: "runtime",
6
6
  description: "Shared assistant runtime with per-surface assistant registration.",
7
7
  dependsOn: [
@@ -74,13 +74,13 @@ export default Object.freeze({
74
74
  mutations: {
75
75
  dependencies: {
76
76
  runtime: {
77
- "@jskit-ai/assistant-core": "0.1.7",
78
- "@jskit-ai/database-runtime": "0.1.31",
79
- "@jskit-ai/http-runtime": "0.1.30",
80
- "@jskit-ai/kernel": "0.1.31",
81
- "@jskit-ai/shell-web": "0.1.30",
82
- "@jskit-ai/users-core": "0.1.41",
83
- "@jskit-ai/users-web": "0.1.46",
77
+ "@jskit-ai/assistant-core": "0.1.9",
78
+ "@jskit-ai/database-runtime": "0.1.33",
79
+ "@jskit-ai/http-runtime": "0.1.32",
80
+ "@jskit-ai/kernel": "0.1.33",
81
+ "@jskit-ai/shell-web": "0.1.32",
82
+ "@jskit-ai/users-core": "0.1.43",
83
+ "@jskit-ai/users-web": "0.1.48",
84
84
  "@tanstack/vue-query": "^5.90.5",
85
85
  "vuetify": "^4.0.0"
86
86
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/assistant-runtime",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./client": "./src/client/index.js",
@@ -8,13 +8,13 @@
8
8
  "./server/actionIds": "./src/server/actionIds.js"
9
9
  },
10
10
  "dependencies": {
11
- "@jskit-ai/assistant-core": "0.1.7",
12
- "@jskit-ai/database-runtime": "0.1.31",
13
- "@jskit-ai/http-runtime": "0.1.30",
14
- "@jskit-ai/kernel": "0.1.31",
15
- "@jskit-ai/shell-web": "0.1.30",
16
- "@jskit-ai/users-core": "0.1.41",
17
- "@jskit-ai/users-web": "0.1.46",
11
+ "@jskit-ai/assistant-core": "0.1.9",
12
+ "@jskit-ai/database-runtime": "0.1.33",
13
+ "@jskit-ai/http-runtime": "0.1.32",
14
+ "@jskit-ai/kernel": "0.1.33",
15
+ "@jskit-ai/shell-web": "0.1.32",
16
+ "@jskit-ai/users-core": "0.1.43",
17
+ "@jskit-ai/users-web": "0.1.48",
18
18
  "@tanstack/vue-query": "^5.90.5",
19
19
  "vuetify": "^4.0.0"
20
20
  }
@@ -25,7 +25,7 @@
25
25
  import { computed, reactive, watch } from "vue";
26
26
  import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
27
27
  import { getClientAppConfig } from "@jskit-ai/kernel/client";
28
- import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
28
+ import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
29
29
  import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
30
30
  import { assistantHttpClient, createAssistantApi, AssistantSettingsFormCard } from "@jskit-ai/assistant-core/client";
31
31
  import { assistantConfigResource, assistantSettingsQueryKey, buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
@@ -65,8 +65,8 @@ const scope = computed(() => {
65
65
  targetSurfaceId: normalizeText(assistantSurface.value?.targetSurfaceId).toLowerCase(),
66
66
  workspaceSlug,
67
67
  workspaceId: settingsRequiresWorkspace
68
- ? Number(placementSnapshot.value?.workspace?.id || 0) || 0
69
- : 0
68
+ ? normalizeRecordId(placementSnapshot.value?.workspace?.id, { fallback: null })
69
+ : null
70
70
  };
71
71
  });
72
72
  const hasScope = computed(() =>
@@ -1,7 +1,7 @@
1
1
  import { computed, ref, watch } from "vue";
2
2
  import { useQueryClient } from "@tanstack/vue-query";
3
3
  import { getClientAppConfig } from "@jskit-ai/kernel/client";
4
- import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
4
+ import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
5
5
  import { buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
6
6
  import {
7
7
  ASSISTANT_STREAM_EVENT_TYPES,
@@ -47,13 +47,13 @@ function buildScopeStorageKey(scope = {}) {
47
47
 
48
48
  function readStoredActiveConversationId(scope = {}) {
49
49
  if (typeof window === "undefined" || !window.sessionStorage) {
50
- return 0;
50
+ return "";
51
51
  }
52
52
 
53
53
  try {
54
- return toPositiveInteger(window.sessionStorage.getItem(buildScopeStorageKey(scope)), 0);
54
+ return normalizeRecordId(window.sessionStorage.getItem(buildScopeStorageKey(scope)), { fallback: "" });
55
55
  } catch {
56
- return 0;
56
+ return "";
57
57
  }
58
58
  }
59
59
 
@@ -62,11 +62,11 @@ function writeStoredActiveConversationId(scope = {}, conversationId) {
62
62
  return;
63
63
  }
64
64
 
65
- const normalizedConversationId = toPositiveInteger(conversationId, 0);
65
+ const normalizedConversationId = normalizeRecordId(conversationId, { fallback: null });
66
66
  const storageKey = buildScopeStorageKey(scope);
67
67
  try {
68
- if (normalizedConversationId > 0) {
69
- window.sessionStorage.setItem(storageKey, String(normalizedConversationId));
68
+ if (normalizedConversationId) {
69
+ window.sessionStorage.setItem(storageKey, normalizedConversationId);
70
70
  return;
71
71
  }
72
72
 
@@ -164,7 +164,8 @@ function mapTranscriptEntriesToAssistantState(entries) {
164
164
  const role = normalizeText(entry?.role).toLowerCase();
165
165
  const kind = normalizeText(entry?.kind).toLowerCase();
166
166
  const metadata = normalizeObject(entry?.metadata);
167
- const messageId = Number(entry?.id) > 0 ? `transcript_${entry.id}` : buildId("transcript");
167
+ const transcriptId = normalizeRecordId(entry?.id, { fallback: null });
168
+ const messageId = transcriptId ? `transcript_${transcriptId}` : buildId("transcript");
168
169
 
169
170
  if (kind === "chat" && (role === "user" || role === "assistant")) {
170
171
  messages.push({
@@ -258,8 +259,8 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
258
259
  targetSurfaceId: normalizeText(assistantSurface.value?.targetSurfaceId).toLowerCase(),
259
260
  workspaceSlug,
260
261
  workspaceId: assistantSurface.value?.runtimeSurfaceRequiresWorkspace
261
- ? toPositiveInteger(placementSnapshot.value?.workspace?.id, 0)
262
- : 0
262
+ ? normalizeRecordId(placementSnapshot.value?.workspace?.id, { fallback: null })
263
+ : null
263
264
  };
264
265
  });
265
266
  const hasRuntimeScope = computed(() =>
@@ -278,7 +279,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
278
279
  resolveSurfaceId: () => normalizeText(currentSurfaceId.value).toLowerCase()
279
280
  });
280
281
 
281
- const activeConversationId = computed(() => normalizeText(conversationId.value));
282
+ const activeConversationId = computed(() => normalizeRecordId(conversationId.value, { fallback: "" }));
282
283
  const isAdminSurface = computed(() => normalizeText(currentSurfaceId.value).toLowerCase() === "admin");
283
284
  const canSend = computed(() => {
284
285
  return Boolean(
@@ -320,8 +321,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
320
321
  }),
321
322
  initialPageParam: null,
322
323
  dedupeBy(entry) {
323
- const conversationNumericId = toPositiveInteger(entry?.id, 0);
324
- return conversationNumericId > 0 ? String(conversationNumericId) : normalizeText(entry?.id);
324
+ return normalizeRecordId(entry?.id, { fallback: normalizeText(entry?.id) });
325
325
  },
326
326
  enabled: computed(() => hasRuntimeScope.value),
327
327
  queryOptions: {
@@ -345,15 +345,15 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
345
345
  return;
346
346
  }
347
347
 
348
- const nextConversationNumericId = toPositiveInteger(nextConversationId, 0);
349
- if (nextConversationNumericId > 0) {
350
- writeStoredActiveConversationId(runtimeScope.value, nextConversationNumericId);
348
+ const nextConversationIdKey = normalizeRecordId(nextConversationId, { fallback: null });
349
+ if (nextConversationIdKey) {
350
+ writeStoredActiveConversationId(runtimeScope.value, nextConversationIdKey);
351
351
  return;
352
352
  }
353
353
 
354
- const previousConversationNumericId = toPositiveInteger(previousConversationId, 0);
355
- if (previousConversationNumericId > 0) {
356
- writeStoredActiveConversationId(runtimeScope.value, 0);
354
+ const previousConversationIdKey = normalizeRecordId(previousConversationId, { fallback: null });
355
+ if (previousConversationIdKey) {
356
+ writeStoredActiveConversationId(runtimeScope.value, "");
357
357
  }
358
358
  });
359
359
 
@@ -378,8 +378,8 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
378
378
  return;
379
379
  }
380
380
 
381
- const activeConversationNumericId = toPositiveInteger(nextConversationId, 0);
382
- if (activeConversationNumericId > 0) {
381
+ const activeConversationIdKey = normalizeRecordId(nextConversationId, { fallback: null });
382
+ if (activeConversationIdKey) {
383
383
  return;
384
384
  }
385
385
 
@@ -394,10 +394,10 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
394
394
  }
395
395
 
396
396
  const hasStoredConversation = sourceEntries.some(
397
- (entry) => toPositiveInteger(entry?.id, 0) === storedConversationId
397
+ (entry) => normalizeRecordId(entry?.id, { fallback: null }) === storedConversationId
398
398
  );
399
399
  if (!hasStoredConversation) {
400
- writeStoredActiveConversationId(nextRuntimeScope, 0);
400
+ writeStoredActiveConversationId(nextRuntimeScope, "");
401
401
  return;
402
402
  }
403
403
 
@@ -458,13 +458,13 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
458
458
  return;
459
459
  }
460
460
 
461
- const parsedConversationId = toPositiveInteger(normalizedConversationId, 0);
461
+ const parsedConversationId = normalizeRecordId(normalizedConversationId, { fallback: null });
462
462
  if (!parsedConversationId) {
463
463
  return;
464
464
  }
465
465
 
466
466
  const previousConversationId = conversationId.value;
467
- conversationId.value = String(parsedConversationId);
467
+ conversationId.value = parsedConversationId;
468
468
  isRestoringConversation.value = true;
469
469
  setRuntimeError("");
470
470
 
@@ -507,7 +507,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
507
507
  input.value = "";
508
508
  setRuntimeError("");
509
509
  conversationId.value = null;
510
- writeStoredActiveConversationId(runtimeScope.value, 0);
510
+ writeStoredActiveConversationId(runtimeScope.value, "");
511
511
  isStreaming.value = false;
512
512
  isRestoringConversation.value = false;
513
513
  abortController.value = null;
@@ -546,7 +546,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
546
546
  const messageId = buildId("message");
547
547
  const assistantMessageId = buildId("assistant");
548
548
  const history = buildHistory(messages.value);
549
- const parsedConversationId = toPositiveInteger(conversationId.value, 0);
549
+ const parsedConversationId = normalizeRecordId(conversationId.value, { fallback: null });
550
550
 
551
551
  appendMessage({
552
552
  id: buildId("user"),
@@ -581,7 +581,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
581
581
  await runtimeApi.streamChat(
582
582
  {
583
583
  messageId,
584
- ...(parsedConversationId > 0 ? { conversationId: parsedConversationId } : {}),
584
+ ...(parsedConversationId ? { conversationId: parsedConversationId } : {}),
585
585
  input: normalizedInput,
586
586
  history
587
587
  },
@@ -591,7 +591,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
591
591
  const eventType = normalizeAssistantStreamEventType(event?.type, "");
592
592
 
593
593
  if (eventType === ASSISTANT_STREAM_EVENT_TYPES.META && Object.hasOwn(event || {}, "conversationId")) {
594
- conversationId.value = event?.conversationId ? String(event.conversationId) : null;
594
+ conversationId.value = normalizeRecordId(event?.conversationId, { fallback: null });
595
595
  return;
596
596
  }
597
597
 
@@ -1,6 +1,6 @@
1
- import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
1
+ import { normalizeDbRecordId, createWithTransaction } from "@jskit-ai/database-runtime/shared";
2
2
  import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
3
- import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
4
4
  import { resolveInsertedId } from "@jskit-ai/assistant-core/server";
5
5
  import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
6
6
 
@@ -9,7 +9,11 @@ function normalizeTargetSurfaceId(value = "") {
9
9
  }
10
10
 
11
11
  function normalizeWorkspaceId(value) {
12
- return parsePositiveInteger(value) || null;
12
+ return normalizeRecordId(value, { fallback: null });
13
+ }
14
+
15
+ function normalizeWorkspaceDbId(value) {
16
+ return normalizeDbRecordId(value, { fallback: null });
13
17
  }
14
18
 
15
19
  function buildScopeKey(targetSurfaceId, workspaceId = null) {
@@ -29,7 +33,7 @@ function mapConfigRow(row = {}) {
29
33
  return {
30
34
  targetSurfaceId: normalizeTargetSurfaceId(row.target_surface_id),
31
35
  scopeKey: normalizeText(row.scope_key),
32
- workspaceId: normalizeWorkspaceId(row.workspace_id),
36
+ workspaceId: normalizeWorkspaceDbId(row.workspace_id),
33
37
  settings: {
34
38
  systemPrompt: String(row.system_prompt || "")
35
39
  }
@@ -57,6 +61,7 @@ function createRepository(knex) {
57
61
  if (!knex || typeof knex !== "function") {
58
62
  throw new Error("createAssistantConfigRepository requires knex client.");
59
63
  }
64
+ const withTransaction = createWithTransaction(knex);
60
65
 
61
66
  async function findByScope({ targetSurfaceId = "", workspaceId = null } = {}, options = {}) {
62
67
  const client = options?.trx || knex;
@@ -139,6 +144,7 @@ function createRepository(knex) {
139
144
  }
140
145
 
141
146
  return Object.freeze({
147
+ withTransaction,
142
148
  createDefaultRecord,
143
149
  findByScope,
144
150
  upsertByScope
@@ -1,7 +1,6 @@
1
- import { runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
2
- import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
1
+ import { createWithTransaction, normalizeDbRecordId, runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
3
2
  import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
4
- import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
5
4
  import {
6
5
  parseJsonObject,
7
6
  resolveInsertedId,
@@ -11,7 +10,15 @@ import {
11
10
  import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
12
11
 
13
12
  function normalizeWorkspaceId(value) {
14
- return parsePositiveInteger(value) || null;
13
+ return normalizeRecordId(value, { fallback: null });
14
+ }
15
+
16
+ function normalizeDbWorkspaceId(value) {
17
+ return normalizeDbRecordId(value, { fallback: null });
18
+ }
19
+
20
+ function normalizeInputRecordId(value) {
21
+ return normalizeRecordId(value, { fallback: null });
15
22
  }
16
23
 
17
24
  function normalizeRequiredSurfaceId(value) {
@@ -34,10 +41,10 @@ function applyWorkspaceScope(query, columnName, workspaceId) {
34
41
 
35
42
  function mapConversationRow(row = {}) {
36
43
  return {
37
- id: Number(row.id),
38
- workspaceId: normalizeWorkspaceId(row.workspace_id),
44
+ id: normalizeDbRecordId(row.id, { fallback: "" }),
45
+ workspaceId: normalizeDbWorkspaceId(row.workspace_id),
39
46
  title: String(row.title || "New conversation"),
40
- createdByUserId: row.created_by_user_id == null ? null : Number(row.created_by_user_id),
47
+ createdByUserId: row.created_by_user_id == null ? null : normalizeDbRecordId(row.created_by_user_id, { fallback: null }),
41
48
  status: String(row.status || "active"),
42
49
  provider: String(row.provider || ""),
43
50
  model: String(row.model || ""),
@@ -52,8 +59,11 @@ function mapConversationRow(row = {}) {
52
59
  }
53
60
 
54
61
  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));
62
+ const cursor = normalizeInputRecordId(pagination.cursor) || "";
63
+ const parsedLimit = Number(pagination.limit);
64
+ const limit = Number.isInteger(parsedLimit) && parsedLimit > 0
65
+ ? Math.max(1, Math.min(maxLimit, parsedLimit))
66
+ : defaultLimit;
57
67
 
58
68
  return {
59
69
  cursor,
@@ -69,16 +79,17 @@ function createRepository(knex) {
69
79
  if (!knex || typeof knex !== "function") {
70
80
  throw new Error("createConversationsRepository requires knex client.");
71
81
  }
82
+ const withTransaction = createWithTransaction(knex);
72
83
 
73
84
  async function findById(conversationId, options = {}) {
74
- const numericConversationId = parsePositiveInteger(conversationId);
75
- if (!numericConversationId) {
85
+ const normalizedConversationId = normalizeInputRecordId(conversationId);
86
+ if (!normalizedConversationId) {
76
87
  return null;
77
88
  }
78
89
 
79
90
  const client = options?.trx || knex;
80
91
  const row = await createConversationBaseQuery(client)
81
- .where("c.id", numericConversationId)
92
+ .where("c.id", normalizedConversationId)
82
93
  .first();
83
94
 
84
95
  return row ? mapConversationRow(row) : null;
@@ -89,17 +100,17 @@ function createRepository(knex) {
89
100
  { workspaceId = null, actorUserId = null, surfaceId = "" } = {},
90
101
  options = {}
91
102
  ) {
92
- const numericConversationId = parsePositiveInteger(conversationId);
93
- const numericActorUserId = parsePositiveInteger(actorUserId);
103
+ const normalizedConversationId = normalizeInputRecordId(conversationId);
104
+ const normalizedActorUserId = normalizeInputRecordId(actorUserId);
94
105
  const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
95
- if (!numericConversationId || !numericActorUserId || !normalizedSurfaceId) {
106
+ if (!normalizedConversationId || !normalizedActorUserId || !normalizedSurfaceId) {
96
107
  return null;
97
108
  }
98
109
 
99
110
  const client = options?.trx || knex;
100
111
  const query = createConversationBaseQuery(client)
101
- .where("c.id", numericConversationId)
102
- .where("c.created_by_user_id", numericActorUserId)
112
+ .where("c.id", normalizedConversationId)
113
+ .where("c.created_by_user_id", normalizedActorUserId)
103
114
  .where("c.surface_id", normalizedSurfaceId);
104
115
  applyWorkspaceScope(query, "c.workspace_id", workspaceId);
105
116
  const row = await query.first();
@@ -113,13 +124,13 @@ function createRepository(knex) {
113
124
  const surfaceId = normalizeRequiredSurfaceId(payload.surfaceId);
114
125
  const insertResult = await client(assistantRuntimeConfig.conversationsTable).insert({
115
126
  workspace_id: normalizeWorkspaceId(payload.workspaceId),
116
- created_by_user_id: parsePositiveInteger(payload.createdByUserId) || null,
127
+ created_by_user_id: normalizeInputRecordId(payload.createdByUserId),
117
128
  title: normalizeText(payload.title) || "New conversation",
118
129
  status: normalizeText(payload.status).toLowerCase() || "active",
119
130
  provider: normalizeText(payload.provider),
120
131
  model: normalizeText(payload.model),
121
132
  surface_id: surfaceId,
122
- message_count: parsePositiveInteger(payload.messageCount) || 0,
133
+ message_count: Math.max(0, Number(payload.messageCount || 0)),
123
134
  metadata_json: stringifyJsonObject(payload.metadata),
124
135
  started_at: payload.startedAt ? new Date(payload.startedAt) : now,
125
136
  ended_at: payload.endedAt ? new Date(payload.endedAt) : null,
@@ -137,8 +148,8 @@ function createRepository(knex) {
137
148
  }
138
149
 
139
150
  async function updateById(conversationId, patch = {}, options = {}) {
140
- const numericConversationId = parsePositiveInteger(conversationId);
141
- if (!numericConversationId) {
151
+ const normalizedConversationId = normalizeInputRecordId(conversationId);
152
+ if (!normalizedConversationId) {
142
153
  return null;
143
154
  }
144
155
 
@@ -173,31 +184,31 @@ function createRepository(knex) {
173
184
  if (Object.keys(updatePatch).length > 0) {
174
185
  updatePatch.updated_at = new Date();
175
186
  await client(assistantRuntimeConfig.conversationsTable)
176
- .where({ id: numericConversationId })
187
+ .where({ id: normalizedConversationId })
177
188
  .update(updatePatch);
178
189
  }
179
190
 
180
- return findById(numericConversationId, {
191
+ return findById(normalizedConversationId, {
181
192
  trx: client
182
193
  });
183
194
  }
184
195
 
185
196
  async function incrementMessageCount(conversationId, delta = 1, options = {}) {
186
- const numericConversationId = parsePositiveInteger(conversationId);
187
- if (!numericConversationId) {
197
+ const normalizedConversationId = normalizeInputRecordId(conversationId);
198
+ if (!normalizedConversationId) {
188
199
  return null;
189
200
  }
190
201
 
191
202
  const client = options?.trx || knex;
192
203
  const incrementBy = Number.isInteger(Number(delta)) ? Number(delta) : 1;
193
204
  await client(assistantRuntimeConfig.conversationsTable)
194
- .where({ id: numericConversationId })
205
+ .where({ id: normalizedConversationId })
195
206
  .update({
196
207
  message_count: client.raw("GREATEST(0, message_count + ?)", [incrementBy]),
197
208
  updated_at: new Date()
198
209
  });
199
210
 
200
- return findById(numericConversationId, {
211
+ return findById(normalizedConversationId, {
201
212
  trx: client
202
213
  });
203
214
  }
@@ -206,9 +217,9 @@ function createRepository(knex) {
206
217
  { workspaceId = null, actorUserId = null, surfaceId = "", pagination = {}, filters = {} } = {},
207
218
  options = {}
208
219
  ) {
209
- const numericActorUserId = parsePositiveInteger(actorUserId);
220
+ const normalizedActorUserId = normalizeInputRecordId(actorUserId);
210
221
  const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
211
- if (!numericActorUserId || !normalizedSurfaceId) {
222
+ if (!normalizedActorUserId || !normalizedSurfaceId) {
212
223
  return {
213
224
  items: [],
214
225
  nextCursor: null
@@ -218,7 +229,7 @@ function createRepository(knex) {
218
229
  const client = options?.trx || knex;
219
230
  const { cursor, limit } = normalizeCursorPagination(pagination);
220
231
  let query = createConversationBaseQuery(client)
221
- .where("c.created_by_user_id", numericActorUserId)
232
+ .where("c.created_by_user_id", normalizedActorUserId)
222
233
  .where("c.surface_id", normalizedSurfaceId);
223
234
  query = applyWorkspaceScope(query, "c.workspace_id", workspaceId);
224
235
 
@@ -226,7 +237,7 @@ function createRepository(knex) {
226
237
  if (normalizedStatus) {
227
238
  query = query.where("c.status", normalizedStatus);
228
239
  }
229
- if (cursor > 0) {
240
+ if (cursor) {
230
241
  query = query.where("c.id", "<", cursor);
231
242
  }
232
243
 
@@ -237,7 +248,9 @@ function createRepository(knex) {
237
248
  const hasMore = rows.length > limit;
238
249
  const pageRows = hasMore ? rows.slice(0, limit) : rows;
239
250
  const items = pageRows.map(mapConversationRow);
240
- const nextCursor = hasMore && pageRows.length > 0 ? String(pageRows[pageRows.length - 1].id) : null;
251
+ const nextCursor = hasMore && pageRows.length > 0
252
+ ? normalizeDbRecordId(pageRows[pageRows.length - 1].id, { fallback: null })
253
+ : null;
241
254
 
242
255
  return {
243
256
  items,
@@ -250,6 +263,7 @@ function createRepository(knex) {
250
263
  }
251
264
 
252
265
  return Object.freeze({
266
+ withTransaction,
253
267
  findById,
254
268
  findByIdForActorScope,
255
269
  create,
@@ -1,6 +1,5 @@
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";
1
+ import { createWithTransaction, normalizeDbRecordId, runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
2
+ import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
4
3
  import {
5
4
  parseJsonObject,
6
5
  resolveInsertedId,
@@ -10,7 +9,15 @@ import {
10
9
  import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
11
10
 
12
11
  function normalizeWorkspaceId(value) {
13
- return parsePositiveInteger(value) || null;
12
+ return normalizeRecordId(value, { fallback: null });
13
+ }
14
+
15
+ function normalizeDbWorkspaceId(value) {
16
+ return normalizeDbRecordId(value, { fallback: null });
17
+ }
18
+
19
+ function normalizeInputRecordId(value) {
20
+ return normalizeRecordId(value, { fallback: null });
14
21
  }
15
22
 
16
23
  function applyWorkspaceScope(query, columnName, workspaceId) {
@@ -24,14 +31,14 @@ function applyWorkspaceScope(query, columnName, workspaceId) {
24
31
 
25
32
  function mapMessageRow(row = {}) {
26
33
  return {
27
- id: Number(row.id),
28
- conversationId: Number(row.conversation_id),
29
- workspaceId: normalizeWorkspaceId(row.workspace_id),
34
+ id: normalizeDbRecordId(row.id, { fallback: "" }),
35
+ conversationId: normalizeDbRecordId(row.conversation_id, { fallback: "" }),
36
+ workspaceId: normalizeDbWorkspaceId(row.workspace_id),
30
37
  seq: Number(row.seq),
31
38
  role: String(row.role || ""),
32
39
  kind: String(row.kind || "chat"),
33
40
  clientMessageSid: String(row.client_message_sid || ""),
34
- actorUserId: row.actor_user_id == null ? null : Number(row.actor_user_id),
41
+ actorUserId: row.actor_user_id == null ? null : normalizeDbRecordId(row.actor_user_id, { fallback: null }),
35
42
  contentText: row.content_text == null ? null : String(row.content_text),
36
43
  metadata: parseJsonObject(row.metadata_json),
37
44
  createdAt: toIso(row.created_at)
@@ -39,8 +46,12 @@ function mapMessageRow(row = {}) {
39
46
  }
40
47
 
41
48
  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));
49
+ const rawPage = Number(pagination.page);
50
+ const rawPageSize = Number(pagination.pageSize);
51
+ const page = Number.isInteger(rawPage) && rawPage > 0 ? rawPage : defaultPage;
52
+ const pageSize = Number.isInteger(rawPageSize) && rawPageSize > 0
53
+ ? Math.max(1, Math.min(maxPageSize, rawPageSize))
54
+ : defaultPageSize;
44
55
 
45
56
  return {
46
57
  page,
@@ -65,16 +76,17 @@ function createRepository(knex) {
65
76
  if (!knex || typeof knex !== "function") {
66
77
  throw new Error("createMessagesRepository requires knex client.");
67
78
  }
79
+ const withTransaction = createWithTransaction(knex);
68
80
 
69
81
  async function findById(messageId, options = {}) {
70
- const numericMessageId = parsePositiveInteger(messageId);
71
- if (!numericMessageId) {
82
+ const normalizedMessageId = normalizeInputRecordId(messageId);
83
+ if (!normalizedMessageId) {
72
84
  return null;
73
85
  }
74
86
 
75
87
  const client = options?.trx || knex;
76
88
  const row = await client(assistantRuntimeConfig.messagesTable)
77
- .where({ id: numericMessageId })
89
+ .where({ id: normalizedMessageId })
78
90
  .first();
79
91
 
80
92
  return row ? mapMessageRow(row) : null;
@@ -82,12 +94,13 @@ function createRepository(knex) {
82
94
 
83
95
  async function create(payload = {}, options = {}) {
84
96
  const client = options?.trx || knex;
85
- const conversationId = parsePositiveInteger(payload.conversationId);
97
+ const conversationId = normalizeInputRecordId(payload.conversationId);
86
98
  if (!conversationId) {
87
99
  throw new TypeError("messagesRepository.create requires conversationId.");
88
100
  }
89
101
 
90
- const seq = parsePositiveInteger(payload.seq) || (await resolveNextSequence(client, conversationId));
102
+ const providedSeq = Number(payload.seq);
103
+ const seq = Number.isInteger(providedSeq) && providedSeq > 0 ? providedSeq : (await resolveNextSequence(client, conversationId));
91
104
  const insertResult = await client(assistantRuntimeConfig.messagesTable).insert({
92
105
  conversation_id: conversationId,
93
106
  workspace_id: normalizeWorkspaceId(payload.workspaceId),
@@ -95,7 +108,7 @@ function createRepository(knex) {
95
108
  role: normalizeText(payload.role).toLowerCase(),
96
109
  kind: normalizeText(payload.kind).toLowerCase() || "chat",
97
110
  client_message_sid: normalizeText(payload.clientMessageSid),
98
- actor_user_id: parsePositiveInteger(payload.actorUserId) || null,
111
+ actor_user_id: normalizeInputRecordId(payload.actorUserId),
99
112
  content_text: payload.contentText == null ? null : String(payload.contentText),
100
113
  metadata_json: stringifyJsonObject(payload.metadata),
101
114
  created_at: payload.createdAt ? new Date(payload.createdAt) : new Date()
@@ -111,14 +124,14 @@ function createRepository(knex) {
111
124
  }
112
125
 
113
126
  async function countByConversationScope(conversationId, { workspaceId = null } = {}, options = {}) {
114
- const numericConversationId = parsePositiveInteger(conversationId);
115
- if (!numericConversationId) {
127
+ const normalizedConversationId = normalizeInputRecordId(conversationId);
128
+ if (!normalizedConversationId) {
116
129
  return 0;
117
130
  }
118
131
 
119
132
  const client = options?.trx || knex;
120
133
  const query = client(assistantRuntimeConfig.messagesTable).where({
121
- conversation_id: numericConversationId
134
+ conversation_id: normalizedConversationId
122
135
  });
123
136
  applyWorkspaceScope(query, "workspace_id", workspaceId);
124
137
  const row = await query.count({ total: "*" }).first();
@@ -128,8 +141,8 @@ function createRepository(knex) {
128
141
  }
129
142
 
130
143
  async function listByConversationScope(conversationId, { workspaceId = null } = {}, pagination = {}, options = {}) {
131
- const numericConversationId = parsePositiveInteger(conversationId);
132
- if (!numericConversationId) {
144
+ const normalizedConversationId = normalizeInputRecordId(conversationId);
145
+ if (!normalizedConversationId) {
133
146
  return [];
134
147
  }
135
148
 
@@ -138,7 +151,7 @@ function createRepository(knex) {
138
151
  const offset = (page - 1) * pageSize;
139
152
 
140
153
  const query = client(assistantRuntimeConfig.messagesTable).where({
141
- conversation_id: numericConversationId
154
+ conversation_id: normalizedConversationId
142
155
  });
143
156
  applyWorkspaceScope(query, "workspace_id", workspaceId);
144
157
  const rows = await query
@@ -155,6 +168,7 @@ function createRepository(knex) {
155
168
  }
156
169
 
157
170
  return Object.freeze({
171
+ withTransaction,
158
172
  findById,
159
173
  create,
160
174
  countByConversationScope,
@@ -1,5 +1,5 @@
1
- import { AppError, parsePositiveInteger, requireAuth } from "@jskit-ai/kernel/server/runtime";
2
- import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
1
+ import { AppError, requireAuth } from "@jskit-ai/kernel/server/runtime";
2
+ import { normalizeObject, normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
3
3
  import { resolveWorkspace } from "@jskit-ai/users-core/server/support/resolveWorkspace";
4
4
  import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
5
5
 
@@ -64,7 +64,7 @@ function createService({ assistantConfigRepository, consoleService = null, appCo
64
64
  }
65
65
 
66
66
  const resolvedWorkspace = workspace || resolveWorkspace(context, input);
67
- const workspaceId = parsePositiveInteger(resolvedWorkspace?.id);
67
+ const workspaceId = normalizeRecordId(resolvedWorkspace?.id, { fallback: null });
68
68
  if (!workspaceId) {
69
69
  throw new AppError(409, "Workspace selection required.");
70
70
  }
@@ -1,5 +1,5 @@
1
- import { AppError, parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
- import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
1
+ import { AppError } from "@jskit-ai/kernel/server/runtime";
2
+ import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
3
  import { resolveWorkspace } from "@jskit-ai/users-core/server/support/resolveWorkspace";
4
4
  import { resolveWorkspaceSlug } from "@jskit-ai/assistant-core/server";
5
5
  import {
@@ -12,8 +12,7 @@ const MAX_INPUT_CHARS = 8000;
12
12
  const MAX_TOOL_ROUNDS = 4;
13
13
 
14
14
  function normalizeConversationId(value) {
15
- const parsed = parsePositiveInteger(value);
16
- return parsed > 0 ? parsed : null;
15
+ return normalizeRecordId(value, { fallback: null });
17
16
  }
18
17
 
19
18
  function normalizeHistory(history = []) {
@@ -1,6 +1,6 @@
1
1
  import { AppError, parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
2
  import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
3
- import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
4
4
  import { normalizeConversationStatus } from "@jskit-ai/assistant-core/shared";
5
5
 
6
6
  const DEFAULT_PAGE_SIZE = 20;
@@ -11,21 +11,21 @@ const DEFAULT_CONVERSATION_TITLE = "New conversation";
11
11
  const MAX_CONVERSATION_TITLE_LENGTH = 80;
12
12
 
13
13
  function resolveWorkspaceId(workspace, { required = false } = {}) {
14
- const workspaceId = parsePositiveInteger(workspace?.id || workspace);
14
+ const workspaceId = normalizeRecordId(workspace?.id || workspace, { fallback: null });
15
15
  if (!workspaceId && required) {
16
16
  throw new AppError(409, "Workspace selection required.");
17
17
  }
18
18
 
19
- return workspaceId || null;
19
+ return workspaceId;
20
20
  }
21
21
 
22
22
  function resolveActorUserId(user, { required = false } = {}) {
23
- const actorUserId = parsePositiveInteger(user?.id);
23
+ const actorUserId = normalizeRecordId(user?.id, { fallback: null });
24
24
  if (!actorUserId && required) {
25
25
  throw new AppError(401, "Authentication required.");
26
26
  }
27
27
 
28
- return actorUserId || null;
28
+ return actorUserId;
29
29
  }
30
30
 
31
31
  function normalizePagination(pagination = {}, { defaultPageSize = DEFAULT_PAGE_SIZE, maxPageSize = MAX_PAGE_SIZE } = {}) {
@@ -39,7 +39,7 @@ function normalizePagination(pagination = {}, { defaultPageSize = DEFAULT_PAGE_S
39
39
  }
40
40
 
41
41
  function normalizeCursorPagination(query = {}, { defaultLimit = DEFAULT_PAGE_SIZE, maxLimit = MAX_PAGE_SIZE } = {}) {
42
- const cursor = parsePositiveInteger(query.cursor) || 0;
42
+ const cursor = normalizeRecordId(query.cursor, { fallback: null });
43
43
  const limit = Math.max(1, Math.min(maxLimit, parsePositiveInteger(query.limit) || defaultLimit));
44
44
 
45
45
  return {
@@ -92,7 +92,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
92
92
  required: true
93
93
  });
94
94
  const source = normalizeObject(options);
95
- const conversationId = parsePositiveInteger(source.conversationId);
95
+ const conversationId = normalizeRecordId(source.conversationId, { fallback: null });
96
96
 
97
97
  if (conversationId) {
98
98
  const existing = await conversationsRepository.findByIdForActorScope(conversationId, {
@@ -143,16 +143,16 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
143
143
 
144
144
  async function appendMessage(assistantSurface, conversationId, payload = {}, options = {}) {
145
145
  const resolvedAssistantSurface = requireAssistantSurface(assistantSurface);
146
- const numericConversationId = parsePositiveInteger(conversationId);
147
- if (!numericConversationId) {
146
+ const normalizedConversationId = normalizeRecordId(conversationId, { fallback: null });
147
+ if (!normalizedConversationId) {
148
148
  throw new TypeError("appendMessage requires conversationId.");
149
149
  }
150
150
 
151
151
  const source = normalizeObject(payload);
152
152
  const context = normalizeObject(options.context);
153
- const actorUserId = parsePositiveInteger(source.actorUserId) || resolveActorUserId(context.actor);
153
+ const actorUserId = normalizeRecordId(source.actorUserId, { fallback: null }) || resolveActorUserId(context.actor);
154
154
  const workspaceId = resolveExpectedWorkspaceId(resolvedAssistantSurface, options.workspace || context.workspace);
155
- const conversation = await conversationsRepository.findByIdForActorScope(numericConversationId, {
155
+ const conversation = await conversationsRepository.findByIdForActorScope(normalizedConversationId, {
156
156
  workspaceId,
157
157
  actorUserId,
158
158
  surfaceId: resolvedAssistantSurface.targetSurfaceId
@@ -162,7 +162,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
162
162
  }
163
163
 
164
164
  const createdMessage = await messagesRepository.create({
165
- conversationId: numericConversationId,
165
+ conversationId: normalizedConversationId,
166
166
  workspaceId: conversation.workspaceId,
167
167
  role: normalizeText(source.role).toLowerCase(),
168
168
  kind: normalizeText(source.kind).toLowerCase() || "chat",
@@ -172,29 +172,29 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
172
172
  metadata: normalizeObject(source.metadata)
173
173
  });
174
174
 
175
- await conversationsRepository.incrementMessageCount(numericConversationId, 1);
175
+ await conversationsRepository.incrementMessageCount(normalizedConversationId, 1);
176
176
 
177
177
  const messageRole = normalizeText(source.role).toLowerCase();
178
178
  const messageKind = normalizeText(source.kind).toLowerCase() || "chat";
179
179
  if (messageRole === "user" && messageKind === "chat" && isDefaultConversationTitle(conversation.title)) {
180
180
  const derivedTitle = deriveConversationTitleFromMessage(source.contentText);
181
181
  if (derivedTitle) {
182
- await conversationsRepository.updateById(numericConversationId, {
182
+ await conversationsRepository.updateById(normalizedConversationId, {
183
183
  title: derivedTitle
184
184
  });
185
185
  }
186
186
  }
187
187
 
188
188
  return {
189
- conversationId: numericConversationId,
189
+ conversationId: normalizedConversationId,
190
190
  message: createdMessage
191
191
  };
192
192
  }
193
193
 
194
194
  async function completeConversation(assistantSurface, conversationId, payload = {}, options = {}) {
195
195
  const resolvedAssistantSurface = requireAssistantSurface(assistantSurface);
196
- const numericConversationId = parsePositiveInteger(conversationId);
197
- if (!numericConversationId) {
196
+ const normalizedConversationId = normalizeRecordId(conversationId, { fallback: null });
197
+ if (!normalizedConversationId) {
198
198
  throw new TypeError("completeConversation requires conversationId.");
199
199
  }
200
200
 
@@ -204,7 +204,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
204
204
  required: true
205
205
  });
206
206
  const workspaceId = resolveExpectedWorkspaceId(resolvedAssistantSurface, options.workspace || context.workspace);
207
- const existing = await conversationsRepository.findByIdForActorScope(numericConversationId, {
207
+ const existing = await conversationsRepository.findByIdForActorScope(normalizedConversationId, {
208
208
  workspaceId,
209
209
  actorUserId,
210
210
  surfaceId: resolvedAssistantSurface.targetSurfaceId
@@ -213,7 +213,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
213
213
  throw new AppError(404, "Conversation not found.");
214
214
  }
215
215
 
216
- return conversationsRepository.updateById(numericConversationId, {
216
+ return conversationsRepository.updateById(normalizedConversationId, {
217
217
  status: normalizeConversationStatus(source.status, {
218
218
  fallback: "completed"
219
219
  }),
@@ -243,18 +243,16 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
243
243
  ...(status ? { status } : {})
244
244
  };
245
245
 
246
- return conversationsRepository.listForActorScope(
247
- {
248
- workspaceId,
249
- actorUserId,
250
- surfaceId: resolvedAssistantSurface.targetSurfaceId,
251
- pagination: {
252
- cursor: pagination.cursor,
253
- limit: pagination.limit
254
- },
255
- filters
256
- }
257
- );
246
+ return conversationsRepository.listForActorScope({
247
+ workspaceId,
248
+ actorUserId,
249
+ surfaceId: resolvedAssistantSurface.targetSurfaceId,
250
+ pagination: {
251
+ cursor: pagination.cursor,
252
+ limit: pagination.limit
253
+ },
254
+ filters
255
+ });
258
256
  }
259
257
 
260
258
  async function getConversationMessagesForUser(assistantSurface, workspace, user, conversationId, query = {}) {
@@ -263,19 +261,19 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
263
261
  const actorUserId = resolveActorUserId(user, {
264
262
  required: true
265
263
  });
266
- const numericConversationId = parsePositiveInteger(conversationId);
267
- if (!numericConversationId) {
264
+ const normalizedConversationId = normalizeRecordId(conversationId, { fallback: null });
265
+ if (!normalizedConversationId) {
268
266
  throw new AppError(400, "Validation failed.", {
269
267
  details: {
270
268
  fieldErrors: {
271
- conversationId: "conversationId must be a positive integer."
269
+ conversationId: "conversationId must be a valid record id."
272
270
  }
273
271
  }
274
272
  });
275
273
  }
276
274
 
277
275
  const conversation = await conversationsRepository.findByIdForActorScope(
278
- numericConversationId,
276
+ normalizedConversationId,
279
277
  {
280
278
  workspaceId,
281
279
  actorUserId,
@@ -291,7 +289,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
291
289
  maxPageSize: MAX_MESSAGES_PAGE_SIZE
292
290
  });
293
291
  const total = await messagesRepository.countByConversationScope(
294
- numericConversationId,
292
+ normalizedConversationId,
295
293
  {
296
294
  workspaceId
297
295
  }
@@ -299,7 +297,7 @@ function createTranscriptService({ conversationsRepository, messagesRepository }
299
297
  const totalPages = Math.max(1, Math.ceil(total / pagination.pageSize));
300
298
  const page = Math.min(pagination.page, totalPages);
301
299
  const entries = await messagesRepository.listByConversationScope(
302
- numericConversationId,
300
+ normalizedConversationId,
303
301
  {
304
302
  workspaceId
305
303
  },
@@ -4,10 +4,10 @@ exports.up = async function up(knex) {
4
4
  const hasWorkspacesTable = await knex.schema.hasTable("workspaces");
5
5
 
6
6
  await knex.schema.createTable("assistant_config", (table) => {
7
- table.increments("id").unsigned().primary();
7
+ table.bigIncrements("id").primary();
8
8
  table.string("target_surface_id", 64).notNullable();
9
9
  table.string("scope_key", 160).notNullable();
10
- table.integer("workspace_id").unsigned().nullable();
10
+ table.bigInteger("workspace_id").unsigned().nullable();
11
11
  if (hasWorkspacesTable) {
12
12
  table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
13
13
  }
@@ -3,12 +3,12 @@ exports.up = async function up(knex) {
3
3
  const hasConversationsTable = await knex.schema.hasTable("assistant_conversations");
4
4
  if (!hasConversationsTable) {
5
5
  await knex.schema.createTable("assistant_conversations", (table) => {
6
- table.increments("id").unsigned().primary();
7
- table.integer("workspace_id").unsigned().nullable();
6
+ table.bigIncrements("id").primary();
7
+ table.bigInteger("workspace_id").unsigned().nullable();
8
8
  if (hasWorkspacesTable) {
9
9
  table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
10
10
  }
11
- table.integer("created_by_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL").index();
11
+ table.bigInteger("created_by_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL").index();
12
12
  table.string("title", 160).notNullable().defaultTo("New conversation");
13
13
  table.string("status", 32).notNullable().defaultTo("active");
14
14
  table.string("provider", 64).notNullable().defaultTo("");
@@ -30,9 +30,9 @@ exports.up = async function up(knex) {
30
30
  const hasMessagesTable = await knex.schema.hasTable("assistant_messages");
31
31
  if (!hasMessagesTable) {
32
32
  await knex.schema.createTable("assistant_messages", (table) => {
33
- table.increments("id").unsigned().primary();
34
- table.integer("conversation_id").unsigned().notNullable().references("id").inTable("assistant_conversations").onDelete("CASCADE");
35
- table.integer("workspace_id").unsigned().nullable();
33
+ table.bigIncrements("id").primary();
34
+ table.bigInteger("conversation_id").unsigned().notNullable().references("id").inTable("assistant_conversations").onDelete("CASCADE");
35
+ table.bigInteger("workspace_id").unsigned().nullable();
36
36
  if (hasWorkspacesTable) {
37
37
  table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
38
38
  }
@@ -40,7 +40,7 @@ exports.up = async function up(knex) {
40
40
  table.string("role", 32).notNullable();
41
41
  table.string("kind", 32).notNullable().defaultTo("chat");
42
42
  table.string("client_message_sid", 128).notNullable().defaultTo("");
43
- table.integer("actor_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL").index();
43
+ table.bigInteger("actor_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL").index();
44
44
  table.text("content_text").nullable();
45
45
  table.text("metadata_json").nullable();
46
46
  table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
@@ -0,0 +1,32 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createRepository as createAssistantConfigRepository } from "../src/server/repositories/assistantConfigRepository.js";
4
+ import { createRepository as createConversationsRepository } from "../src/server/repositories/conversationsRepository.js";
5
+ import { createRepository as createMessagesRepository } from "../src/server/repositories/messagesRepository.js";
6
+
7
+ function createKnexStub() {
8
+ const knex = Object.assign(() => {
9
+ throw new Error("query execution not expected");
10
+ }, {
11
+ async transaction(work) {
12
+ return work({ trxId: "trx-1" });
13
+ }
14
+ });
15
+
16
+ return knex;
17
+ }
18
+
19
+ test("assistant-runtime repositories expose withTransaction", async () => {
20
+ const knex = createKnexStub();
21
+ const repositories = [
22
+ createAssistantConfigRepository(knex),
23
+ createConversationsRepository(knex),
24
+ createMessagesRepository(knex)
25
+ ];
26
+
27
+ for (const repository of repositories) {
28
+ assert.equal(typeof repository.withTransaction, "function");
29
+ const result = await repository.withTransaction(async (trx) => ({ id: trx.trxId }));
30
+ assert.deepEqual(result, { id: "trx-1" });
31
+ }
32
+ });