@jskit-ai/assistant 0.1.37 → 0.1.39

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 (29) hide show
  1. package/package.descriptor.mjs +84 -319
  2. package/package.json +2 -2
  3. package/src/server/buildTemplateContext.js +39 -14
  4. package/templates/src/pages/assistant/index.vue +2 -2
  5. package/templates/src/pages/settings/assistant/index.vue +7 -0
  6. package/test/buildTemplateContext.test.js +42 -27
  7. package/test/packageDescriptor.test.js +26 -55
  8. package/test/templateContracts.test.js +8 -6
  9. package/templates/migrations/assistant_config_initial.cjs +0 -25
  10. package/templates/migrations/assistant_transcripts_initial.cjs +0 -56
  11. package/templates/src/local-package/client/components/AssistantSettingsClientElement.vue +0 -88
  12. package/templates/src/local-package/client/components/AssistantSurfaceClientElement.vue +0 -10
  13. package/templates/src/local-package/client/composables/useAssistantRuntime.js +0 -754
  14. package/templates/src/local-package/client/index.js +0 -4
  15. package/templates/src/local-package/client/providers/AssistantClientProvider.js +0 -16
  16. package/templates/src/local-package/package.descriptor.mjs +0 -85
  17. package/templates/src/local-package/package.json +0 -11
  18. package/templates/src/local-package/server/AssistantProvider.js +0 -143
  19. package/templates/src/local-package/server/actionIds.js +0 -9
  20. package/templates/src/local-package/server/actions.js +0 -183
  21. package/templates/src/local-package/server/registerRoutes.js +0 -296
  22. package/templates/src/local-package/server/repositories/assistantConfigRepository.js +0 -141
  23. package/templates/src/local-package/server/repositories/conversationsRepository.js +0 -240
  24. package/templates/src/local-package/server/repositories/messagesRepository.js +0 -166
  25. package/templates/src/local-package/server/services/assistantConfigService.js +0 -90
  26. package/templates/src/local-package/server/services/chatService.js +0 -995
  27. package/templates/src/local-package/server/services/transcriptService.js +0 -308
  28. package/templates/src/local-package/shared/assistantRuntimeConfig.js +0 -13
  29. package/templates/src/local-package/shared/index.js +0 -1
@@ -1,296 +0,0 @@
1
- import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
2
- import { buildWorkspaceInputFromRouteParams } from "@jskit-ai/users-core/server/support/workspaceRouteInput";
3
- import { workspaceSlugParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
4
- import {
5
- assistantConfigResource,
6
- assistantResource,
7
- resolveAssistantApiBasePath,
8
- resolveAssistantSettingsApiPath
9
- } from "@jskit-ai/assistant-core/shared";
10
- import {
11
- endNdjson,
12
- mapStreamError,
13
- setNdjsonHeaders,
14
- writeNdjson
15
- } from "@jskit-ai/assistant-core/server";
16
- import { assistantRuntimeConfig } from "../shared/assistantRuntimeConfig.js";
17
- import { actionIds } from "./actionIds.js";
18
-
19
- const runtimeVisibility = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace ? "workspace" : "public";
20
- const settingsVisibility = assistantRuntimeConfig.settingsSurfaceRequiresWorkspace ? "workspace" : "public";
21
- const runtimeRouteBase = resolveAssistantApiBasePath({
22
- requiresWorkspace: assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
23
- });
24
- const settingsRouteBase = resolveAssistantSettingsApiPath({
25
- requiresWorkspace: assistantRuntimeConfig.settingsSurfaceRequiresWorkspace
26
- });
27
-
28
- function buildWorkspaceRouteConfig(requiresWorkspace, baseConfig = {}) {
29
- if (requiresWorkspace !== true) {
30
- return baseConfig;
31
- }
32
-
33
- return {
34
- ...baseConfig,
35
- paramsValidator: workspaceSlugParamsValidator
36
- };
37
- }
38
-
39
- function readWorkspaceInput(request, requiresWorkspace) {
40
- if (requiresWorkspace !== true) {
41
- return {};
42
- }
43
-
44
- return buildWorkspaceInputFromRouteParams(request?.input?.params);
45
- }
46
-
47
- function registerRoutes(app) {
48
- if (!app || typeof app.make !== "function") {
49
- throw new Error("registerRoutes requires application make().");
50
- }
51
-
52
- const router = app.make("jskit.http.router");
53
-
54
- router.register(
55
- "GET",
56
- settingsRouteBase,
57
- buildWorkspaceRouteConfig(assistantRuntimeConfig.settingsSurfaceRequiresWorkspace, {
58
- auth: "required",
59
- surface: assistantRuntimeConfig.settingsSurfaceId,
60
- visibility: settingsVisibility,
61
- meta: {
62
- tags: ["assistant", "settings"],
63
- summary: "Get assistant settings."
64
- },
65
- responseValidators: withStandardErrorResponses({
66
- 200: assistantConfigResource.operations.view.outputValidator
67
- })
68
- }),
69
- async function assistantSettingsReadRoute(request, reply) {
70
- const response = await request.executeAction({
71
- actionId: actionIds.settingsRead,
72
- input: {
73
- ...readWorkspaceInput(request, assistantRuntimeConfig.settingsSurfaceRequiresWorkspace)
74
- }
75
- });
76
-
77
- reply.code(200).send(response);
78
- }
79
- );
80
-
81
- router.register(
82
- "PATCH",
83
- settingsRouteBase,
84
- buildWorkspaceRouteConfig(assistantRuntimeConfig.settingsSurfaceRequiresWorkspace, {
85
- auth: "required",
86
- surface: assistantRuntimeConfig.settingsSurfaceId,
87
- visibility: settingsVisibility,
88
- meta: {
89
- tags: ["assistant", "settings"],
90
- summary: "Update assistant settings."
91
- },
92
- bodyValidator: assistantConfigResource.operations.patch.bodyValidator,
93
- responseValidators: withStandardErrorResponses(
94
- {
95
- 200: assistantConfigResource.operations.patch.outputValidator
96
- },
97
- {
98
- includeValidation400: true
99
- }
100
- )
101
- }),
102
- async function assistantSettingsPatchRoute(request, reply) {
103
- const response = await request.executeAction({
104
- actionId: actionIds.settingsUpdate,
105
- input: {
106
- ...readWorkspaceInput(request, assistantRuntimeConfig.settingsSurfaceRequiresWorkspace),
107
- patch: request.input.body
108
- }
109
- });
110
-
111
- reply.code(200).send(response);
112
- }
113
- );
114
-
115
- router.register(
116
- "POST",
117
- `${runtimeRouteBase}/chat/stream`,
118
- buildWorkspaceRouteConfig(assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace, {
119
- auth: "required",
120
- surface: assistantRuntimeConfig.runtimeSurfaceId,
121
- visibility: runtimeVisibility,
122
- meta: {
123
- tags: ["assistant"],
124
- summary: "Stream assistant response."
125
- },
126
- bodyValidator: assistantResource.operations.chatStream.bodyValidator
127
- }),
128
- async function assistantChatStreamRoute(request, reply) {
129
- const abortController = new AbortController();
130
- const requestBody = request?.input?.body && typeof request.input.body === "object" ? request.input.body : {};
131
- const closeListener = () => {
132
- abortController.abort();
133
- };
134
-
135
- let streamStarted = false;
136
-
137
- function ensureStreamStarted() {
138
- if (streamStarted) {
139
- return;
140
- }
141
-
142
- setNdjsonHeaders(reply);
143
- reply.code(200);
144
- reply.hijack();
145
- if (typeof reply.raw.flushHeaders === "function") {
146
- reply.raw.flushHeaders();
147
- }
148
- streamStarted = true;
149
- }
150
-
151
- const streamWriter = Object.freeze({
152
- sendMeta(payload = {}) {
153
- ensureStreamStarted();
154
- writeNdjson(reply, payload);
155
- },
156
- sendAssistantDelta(payload = {}) {
157
- ensureStreamStarted();
158
- writeNdjson(reply, payload);
159
- },
160
- sendAssistantMessage(payload = {}) {
161
- ensureStreamStarted();
162
- writeNdjson(reply, payload);
163
- },
164
- sendToolCall(payload = {}) {
165
- ensureStreamStarted();
166
- writeNdjson(reply, payload);
167
- },
168
- sendToolResult(payload = {}) {
169
- ensureStreamStarted();
170
- writeNdjson(reply, payload);
171
- },
172
- sendError(payload = {}) {
173
- ensureStreamStarted();
174
- writeNdjson(reply, payload);
175
- },
176
- sendDone(payload = {}) {
177
- ensureStreamStarted();
178
- writeNdjson(reply, payload);
179
- }
180
- });
181
-
182
- try {
183
- request.raw.on("close", closeListener);
184
-
185
- await request.executeAction({
186
- actionId: actionIds.chatStream,
187
- input: {
188
- ...readWorkspaceInput(request, assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace),
189
- messageId: requestBody.messageId,
190
- conversationId: requestBody.conversationId,
191
- input: requestBody.input,
192
- history: requestBody.history,
193
- clientContext: requestBody.clientContext
194
- },
195
- deps: {
196
- streamWriter,
197
- abortSignal: abortController.signal
198
- }
199
- });
200
-
201
- if (streamStarted) {
202
- endNdjson(reply);
203
- return;
204
- }
205
-
206
- reply.code(204).send();
207
- } catch (error) {
208
- if (!streamStarted) {
209
- const statusCode = Number(error?.status || error?.statusCode || 500);
210
- const safeStatusCode = Number.isInteger(statusCode) && statusCode >= 400 && statusCode <= 599 ? statusCode : 500;
211
- reply.code(safeStatusCode).send({
212
- error: safeStatusCode >= 500 ? "Internal server error." : String(error?.message || "Request failed.")
213
- });
214
- return;
215
- }
216
-
217
- const streamError = mapStreamError(error);
218
- writeNdjson(reply, {
219
- type: "error",
220
- ...streamError
221
- });
222
- writeNdjson(reply, {
223
- type: "done",
224
- status: "failed"
225
- });
226
- endNdjson(reply);
227
- } finally {
228
- request.raw.off("close", closeListener);
229
- }
230
- }
231
- );
232
-
233
- router.register(
234
- "GET",
235
- `${runtimeRouteBase}/conversations`,
236
- buildWorkspaceRouteConfig(assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace, {
237
- auth: "required",
238
- surface: assistantRuntimeConfig.runtimeSurfaceId,
239
- visibility: runtimeVisibility,
240
- meta: {
241
- tags: ["assistant"],
242
- summary: "List assistant conversations."
243
- },
244
- queryValidator: assistantResource.operations.conversationsList.queryValidator,
245
- responseValidators: withStandardErrorResponses({
246
- 200: assistantResource.operations.conversationsList.outputValidator
247
- })
248
- }),
249
- async function assistantConversationsRoute(request, reply) {
250
- const response = await request.executeAction({
251
- actionId: actionIds.conversationsList,
252
- input: {
253
- ...readWorkspaceInput(request, assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace),
254
- query: request.input.query
255
- }
256
- });
257
-
258
- reply.code(200).send(response);
259
- }
260
- );
261
-
262
- router.register(
263
- "GET",
264
- `${runtimeRouteBase}/conversations/:conversationId/messages`,
265
- buildWorkspaceRouteConfig(assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace, {
266
- auth: "required",
267
- surface: assistantRuntimeConfig.runtimeSurfaceId,
268
- visibility: runtimeVisibility,
269
- meta: {
270
- tags: ["assistant"],
271
- summary: "List assistant conversation messages."
272
- },
273
- paramsValidator: assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
274
- ? [workspaceSlugParamsValidator, assistantResource.operations.conversationMessagesList.paramsValidator]
275
- : assistantResource.operations.conversationMessagesList.paramsValidator,
276
- queryValidator: assistantResource.operations.conversationMessagesList.queryValidator,
277
- responseValidators: withStandardErrorResponses({
278
- 200: assistantResource.operations.conversationMessagesList.outputValidator
279
- })
280
- }),
281
- async function assistantConversationMessagesRoute(request, reply) {
282
- const response = await request.executeAction({
283
- actionId: actionIds.conversationMessagesList,
284
- input: {
285
- ...readWorkspaceInput(request, assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace),
286
- conversationId: request.input.params.conversationId,
287
- query: request.input.query
288
- }
289
- });
290
-
291
- reply.code(200).send(response);
292
- }
293
- );
294
- }
295
-
296
- export { registerRoutes };
@@ -1,141 +0,0 @@
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,240 +0,0 @@
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 mapConversationRow(row = {}) {
26
- return {
27
- id: Number(row.id),
28
- workspaceId: normalizeWorkspaceId(row.workspace_id),
29
- title: String(row.title || "New conversation"),
30
- createdByUserId: row.created_by_user_id == null ? null : Number(row.created_by_user_id),
31
- status: String(row.status || "active"),
32
- provider: String(row.provider || ""),
33
- model: String(row.model || ""),
34
- surfaceId: String(row.surface_id || ""),
35
- startedAt: toIso(row.started_at),
36
- endedAt: row.ended_at ? toIso(row.ended_at) : null,
37
- messageCount: Number(row.message_count || 0),
38
- metadata: parseJsonObject(row.metadata_json),
39
- createdAt: toIso(row.created_at),
40
- updatedAt: toIso(row.updated_at)
41
- };
42
- }
43
-
44
- function normalizeCursorPagination(pagination = {}, { defaultLimit = 20, maxLimit = 200 } = {}) {
45
- const cursor = parsePositiveInteger(pagination.cursor) || 0;
46
- const limit = Math.max(1, Math.min(maxLimit, parsePositiveInteger(pagination.limit) || defaultLimit));
47
-
48
- return {
49
- cursor,
50
- limit
51
- };
52
- }
53
-
54
- function createConversationBaseQuery(client) {
55
- return client(`${assistantRuntimeConfig.conversationsTable} as c`).select("c.*");
56
- }
57
-
58
- function createRepository(knex) {
59
- if (!knex || typeof knex !== "function") {
60
- throw new Error("createConversationsRepository requires knex client.");
61
- }
62
-
63
- async function findById(conversationId, options = {}) {
64
- const numericConversationId = parsePositiveInteger(conversationId);
65
- if (!numericConversationId) {
66
- return null;
67
- }
68
-
69
- const client = options?.trx || knex;
70
- const row = await createConversationBaseQuery(client)
71
- .where("c.id", numericConversationId)
72
- .first();
73
-
74
- return row ? mapConversationRow(row) : null;
75
- }
76
-
77
- async function findByIdForActorScope(conversationId, { workspaceId = null, actorUserId = null } = {}, options = {}) {
78
- const numericConversationId = parsePositiveInteger(conversationId);
79
- const numericActorUserId = parsePositiveInteger(actorUserId);
80
- if (!numericConversationId || !numericActorUserId) {
81
- return null;
82
- }
83
-
84
- const client = options?.trx || knex;
85
- const query = createConversationBaseQuery(client)
86
- .where("c.id", numericConversationId)
87
- .where("c.created_by_user_id", numericActorUserId);
88
- applyWorkspaceScope(query, "c.workspace_id", workspaceId);
89
- const row = await query.first();
90
-
91
- return row ? mapConversationRow(row) : null;
92
- }
93
-
94
- async function create(payload = {}, options = {}) {
95
- const client = options?.trx || knex;
96
- const now = new Date();
97
- const insertResult = await client(assistantRuntimeConfig.conversationsTable).insert({
98
- workspace_id: normalizeWorkspaceId(payload.workspaceId),
99
- created_by_user_id: parsePositiveInteger(payload.createdByUserId) || null,
100
- title: normalizeText(payload.title) || "New conversation",
101
- status: normalizeText(payload.status).toLowerCase() || "active",
102
- provider: normalizeText(payload.provider),
103
- model: normalizeText(payload.model),
104
- surface_id: normalizeText(payload.surfaceId).toLowerCase() || assistantRuntimeConfig.runtimeSurfaceId,
105
- message_count: parsePositiveInteger(payload.messageCount) || 0,
106
- metadata_json: stringifyJsonObject(payload.metadata),
107
- started_at: payload.startedAt ? new Date(payload.startedAt) : now,
108
- ended_at: payload.endedAt ? new Date(payload.endedAt) : null,
109
- created_at: now,
110
- updated_at: now
111
- });
112
- const id = resolveInsertedId(insertResult);
113
- if (!id) {
114
- throw new Error("conversationsRepository.create could not resolve inserted id.");
115
- }
116
-
117
- return findById(id, {
118
- trx: client
119
- });
120
- }
121
-
122
- async function updateById(conversationId, patch = {}, options = {}) {
123
- const numericConversationId = parsePositiveInteger(conversationId);
124
- if (!numericConversationId) {
125
- return null;
126
- }
127
-
128
- const client = options?.trx || knex;
129
- const updatePatch = {};
130
-
131
- if (Object.hasOwn(patch, "title")) {
132
- updatePatch.title = normalizeText(patch.title) || "New conversation";
133
- }
134
- if (Object.hasOwn(patch, "status")) {
135
- updatePatch.status = normalizeText(patch.status).toLowerCase() || "active";
136
- }
137
- if (Object.hasOwn(patch, "provider")) {
138
- updatePatch.provider = normalizeText(patch.provider);
139
- }
140
- if (Object.hasOwn(patch, "model")) {
141
- updatePatch.model = normalizeText(patch.model);
142
- }
143
- if (Object.hasOwn(patch, "surfaceId")) {
144
- updatePatch.surface_id = normalizeText(patch.surfaceId).toLowerCase() || assistantRuntimeConfig.runtimeSurfaceId;
145
- }
146
- if (Object.hasOwn(patch, "messageCount")) {
147
- updatePatch.message_count = Math.max(0, Number(patch.messageCount || 0));
148
- }
149
- if (Object.hasOwn(patch, "endedAt")) {
150
- updatePatch.ended_at = patch.endedAt ? new Date(patch.endedAt) : null;
151
- }
152
- if (Object.hasOwn(patch, "metadata")) {
153
- updatePatch.metadata_json = stringifyJsonObject(patch.metadata);
154
- }
155
-
156
- if (Object.keys(updatePatch).length > 0) {
157
- updatePatch.updated_at = new Date();
158
- await client(assistantRuntimeConfig.conversationsTable)
159
- .where({ id: numericConversationId })
160
- .update(updatePatch);
161
- }
162
-
163
- return findById(numericConversationId, {
164
- trx: client
165
- });
166
- }
167
-
168
- async function incrementMessageCount(conversationId, delta = 1, options = {}) {
169
- const numericConversationId = parsePositiveInteger(conversationId);
170
- if (!numericConversationId) {
171
- return null;
172
- }
173
-
174
- const client = options?.trx || knex;
175
- const incrementBy = Number.isInteger(Number(delta)) ? Number(delta) : 1;
176
- await client(assistantRuntimeConfig.conversationsTable)
177
- .where({ id: numericConversationId })
178
- .update({
179
- message_count: client.raw("GREATEST(0, message_count + ?)", [incrementBy]),
180
- updated_at: new Date()
181
- });
182
-
183
- return findById(numericConversationId, {
184
- trx: client
185
- });
186
- }
187
-
188
- async function listForActorScope({ workspaceId = null, actorUserId = null, pagination = {}, filters = {} } = {}, options = {}) {
189
- const numericActorUserId = parsePositiveInteger(actorUserId);
190
- if (!numericActorUserId) {
191
- return {
192
- items: [],
193
- nextCursor: null
194
- };
195
- }
196
-
197
- const client = options?.trx || knex;
198
- const { cursor, limit } = normalizeCursorPagination(pagination);
199
- let query = createConversationBaseQuery(client).where("c.created_by_user_id", numericActorUserId);
200
- query = applyWorkspaceScope(query, "c.workspace_id", workspaceId);
201
-
202
- const normalizedStatus = normalizeText(filters.status).toLowerCase();
203
- if (normalizedStatus) {
204
- query = query.where("c.status", normalizedStatus);
205
- }
206
- if (cursor > 0) {
207
- query = query.where("c.id", "<", cursor);
208
- }
209
-
210
- const rows = await query
211
- .orderBy("c.id", "desc")
212
- .limit(limit + 1);
213
-
214
- const hasMore = rows.length > limit;
215
- const pageRows = hasMore ? rows.slice(0, limit) : rows;
216
- const items = pageRows.map(mapConversationRow);
217
- const nextCursor = hasMore && pageRows.length > 0 ? String(pageRows[pageRows.length - 1].id) : null;
218
-
219
- return {
220
- items,
221
- nextCursor
222
- };
223
- }
224
-
225
- async function transaction(callback) {
226
- return runInTransaction(knex, callback);
227
- }
228
-
229
- return Object.freeze({
230
- findById,
231
- findByIdForActorScope,
232
- create,
233
- updateById,
234
- incrementMessageCount,
235
- listForActorScope,
236
- transaction
237
- });
238
- }
239
-
240
- export { createRepository };