@jskit-ai/assistant-runtime 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.descriptor.mjs +136 -0
- package/package.json +21 -0
- package/src/client/components/AssistantSettingsClientElement.vue +204 -0
- package/src/client/components/AssistantSurfaceClientElement.vue +19 -0
- package/src/client/composables/useAssistantRuntime.js +759 -0
- package/src/client/index.js +4 -0
- package/src/client/providers/AssistantClientProvider.js +16 -0
- package/src/server/AssistantProvider.js +152 -0
- package/src/server/actionIds.js +9 -0
- package/src/server/actions.js +151 -0
- package/src/server/inputValidators.js +41 -0
- package/src/server/registerRoutes.js +450 -0
- package/src/server/repositories/assistantConfigRepository.js +148 -0
- package/src/server/repositories/conversationsRepository.js +263 -0
- package/src/server/repositories/messagesRepository.js +166 -0
- package/src/server/services/assistantConfigService.js +132 -0
- package/src/server/services/chatService.js +1048 -0
- package/src/server/services/transcriptService.js +331 -0
- package/src/server/support/assistantServerConfig.js +106 -0
- package/src/server/support/createSurfaceAwareToolCatalog.js +64 -0
- package/src/shared/assistantRuntimeConfig.js +7 -0
- package/src/shared/assistantSurfaces.js +97 -0
- package/src/shared/index.js +7 -0
- package/templates/migrations/assistant_config_initial.cjs +27 -0
- package/templates/migrations/assistant_transcripts_initial.cjs +58 -0
- package/test/assistantServerConfig.test.js +72 -0
- package/test/assistantSurfaces.test.js +50 -0
- package/test/createSurfaceAwareToolCatalog.test.js +77 -0
- package/test/lazyAppConfig.test.js +248 -0
- package/test/packageDescriptor.test.js +34 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
|
|
3
|
+
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
4
|
+
import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
|
|
5
|
+
import { buildWorkspaceInputFromRouteParams } from "@jskit-ai/users-core/server/support/workspaceRouteInput";
|
|
6
|
+
import { workspaceSlugParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
|
|
7
|
+
import {
|
|
8
|
+
assistantConfigResource,
|
|
9
|
+
assistantResource,
|
|
10
|
+
resolveAssistantApiBasePath
|
|
11
|
+
} from "@jskit-ai/assistant-core/shared";
|
|
12
|
+
import {
|
|
13
|
+
endNdjson,
|
|
14
|
+
mapStreamError,
|
|
15
|
+
setNdjsonHeaders,
|
|
16
|
+
writeNdjson
|
|
17
|
+
} from "@jskit-ai/assistant-core/server";
|
|
18
|
+
import { resolveAssistantSurfaceConfig } from "../shared/assistantSurfaces.js";
|
|
19
|
+
import { actionIds } from "./actionIds.js";
|
|
20
|
+
import { assistantSurfaceRouteParamsValidator } from "./inputValidators.js";
|
|
21
|
+
|
|
22
|
+
function buildRouteParamsValidator(requiresWorkspace) {
|
|
23
|
+
if (requiresWorkspace === true) {
|
|
24
|
+
return [workspaceSlugParamsValidator, assistantSurfaceRouteParamsValidator];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return assistantSurfaceRouteParamsValidator;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildConversationMessagesRouteParamsValidator(requiresWorkspace) {
|
|
31
|
+
const validators = buildRouteParamsValidator(requiresWorkspace);
|
|
32
|
+
if (Array.isArray(validators)) {
|
|
33
|
+
return validators.concat(assistantResource.operations.conversationMessagesList.paramsValidator);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return [validators, assistantResource.operations.conversationMessagesList.paramsValidator];
|
|
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 requireAssistantSurface(appConfig = {}, targetSurfaceId = "") {
|
|
48
|
+
const assistantSurface = resolveAssistantSurfaceConfig(appConfig, targetSurfaceId);
|
|
49
|
+
if (assistantSurface) {
|
|
50
|
+
return assistantSurface;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new AppError(404, "Assistant not found.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function requireHostSurfaceId(request) {
|
|
57
|
+
const headerValue = Array.isArray(request?.headers?.["x-jskit-surface"])
|
|
58
|
+
? request.headers["x-jskit-surface"][0]
|
|
59
|
+
: request?.headers?.["x-jskit-surface"];
|
|
60
|
+
const hostSurfaceId = normalizeSurfaceId(headerValue);
|
|
61
|
+
if (hostSurfaceId) {
|
|
62
|
+
return hostSurfaceId;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
throw new AppError(400, "Assistant surface header x-jskit-surface is required.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function shouldExposeAppErrorDetails(errorCode = "") {
|
|
69
|
+
return String(errorCode || "").trim() !== "ACTION_PERMISSION_DENIED";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sendPreStreamErrorResponse(reply, error) {
|
|
73
|
+
const statusCode = Number(error?.status || error?.statusCode || 500);
|
|
74
|
+
const safeStatusCode = Number.isInteger(statusCode) && statusCode >= 400 && statusCode <= 599 ? statusCode : 500;
|
|
75
|
+
|
|
76
|
+
if (error instanceof AppError) {
|
|
77
|
+
const appErrorCode = String(error?.code || "app_error").trim() || "app_error";
|
|
78
|
+
const payload = {
|
|
79
|
+
error: error.message,
|
|
80
|
+
code: appErrorCode
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (error.details && shouldExposeAppErrorDetails(appErrorCode)) {
|
|
84
|
+
payload.details = error.details;
|
|
85
|
+
if (error.details.fieldErrors) {
|
|
86
|
+
payload.fieldErrors = error.details.fieldErrors;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (error.headers && typeof error.headers === "object") {
|
|
91
|
+
Object.entries(error.headers).forEach(([name, value]) => {
|
|
92
|
+
reply.header(name, value);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
reply.code(safeStatusCode).send(payload);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
reply.code(safeStatusCode).send({
|
|
101
|
+
error: safeStatusCode >= 500 ? "Internal server error." : String(error?.message || "Request failed.")
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolveRouteRequestState(request, { resolveCurrentAppConfig = () => ({}), kind = "runtime", requiresWorkspace = false } = {}) {
|
|
106
|
+
const appConfig = resolveCurrentAppConfig();
|
|
107
|
+
const targetSurfaceId = normalizeSurfaceId(request?.input?.params?.surfaceId);
|
|
108
|
+
const assistantSurface = requireAssistantSurface(appConfig, targetSurfaceId);
|
|
109
|
+
const hostSurfaceId = requireHostSurfaceId(request);
|
|
110
|
+
const expectsWorkspace = kind === "settings"
|
|
111
|
+
? assistantSurface.settingsSurfaceRequiresWorkspace
|
|
112
|
+
: assistantSurface.runtimeSurfaceRequiresWorkspace;
|
|
113
|
+
const expectedHostSurfaceId = kind === "settings"
|
|
114
|
+
? assistantSurface.settingsSurfaceId
|
|
115
|
+
: assistantSurface.targetSurfaceId;
|
|
116
|
+
|
|
117
|
+
if (expectsWorkspace !== (requiresWorkspace === true)) {
|
|
118
|
+
throw new AppError(404, "Assistant route not found.");
|
|
119
|
+
}
|
|
120
|
+
if (hostSurfaceId !== expectedHostSurfaceId) {
|
|
121
|
+
throw new AppError(403, "Assistant route is not available on this surface.");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
assistantSurface,
|
|
126
|
+
hostSurfaceId,
|
|
127
|
+
actionInput: Object.freeze({
|
|
128
|
+
targetSurfaceId: assistantSurface.targetSurfaceId,
|
|
129
|
+
...readWorkspaceInput(request, requiresWorkspace)
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function registerSettingsRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false } = {}) {
|
|
135
|
+
const routeBase = resolveAssistantApiBasePath({
|
|
136
|
+
requiresWorkspace
|
|
137
|
+
});
|
|
138
|
+
const visibility = requiresWorkspace ? "workspace" : "public";
|
|
139
|
+
const routePath = `${routeBase}/:surfaceId/settings`;
|
|
140
|
+
const paramsValidator = buildRouteParamsValidator(requiresWorkspace);
|
|
141
|
+
|
|
142
|
+
router.register(
|
|
143
|
+
"GET",
|
|
144
|
+
routePath,
|
|
145
|
+
{
|
|
146
|
+
auth: "required",
|
|
147
|
+
visibility,
|
|
148
|
+
paramsValidator,
|
|
149
|
+
meta: {
|
|
150
|
+
tags: ["assistant", "settings"],
|
|
151
|
+
summary: "Get assistant settings."
|
|
152
|
+
},
|
|
153
|
+
responseValidators: withStandardErrorResponses({
|
|
154
|
+
200: assistantConfigResource.operations.view.outputValidator
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
async function assistantSettingsReadRoute(request, reply) {
|
|
158
|
+
const routeState = resolveRouteRequestState(request, {
|
|
159
|
+
resolveCurrentAppConfig,
|
|
160
|
+
kind: "settings",
|
|
161
|
+
requiresWorkspace
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const response = await request.executeAction({
|
|
165
|
+
actionId: actionIds.settingsRead,
|
|
166
|
+
context: {
|
|
167
|
+
surface: routeState.hostSurfaceId
|
|
168
|
+
},
|
|
169
|
+
input: routeState.actionInput
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
reply.code(200).send(response);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
router.register(
|
|
177
|
+
"PATCH",
|
|
178
|
+
routePath,
|
|
179
|
+
{
|
|
180
|
+
auth: "required",
|
|
181
|
+
visibility,
|
|
182
|
+
paramsValidator,
|
|
183
|
+
meta: {
|
|
184
|
+
tags: ["assistant", "settings"],
|
|
185
|
+
summary: "Update assistant settings."
|
|
186
|
+
},
|
|
187
|
+
bodyValidator: assistantConfigResource.operations.patch.bodyValidator,
|
|
188
|
+
responseValidators: withStandardErrorResponses(
|
|
189
|
+
{
|
|
190
|
+
200: assistantConfigResource.operations.patch.outputValidator
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
includeValidation400: true
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
},
|
|
197
|
+
async function assistantSettingsPatchRoute(request, reply) {
|
|
198
|
+
const routeState = resolveRouteRequestState(request, {
|
|
199
|
+
resolveCurrentAppConfig,
|
|
200
|
+
kind: "settings",
|
|
201
|
+
requiresWorkspace
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const response = await request.executeAction({
|
|
205
|
+
actionId: actionIds.settingsUpdate,
|
|
206
|
+
context: {
|
|
207
|
+
surface: routeState.hostSurfaceId
|
|
208
|
+
},
|
|
209
|
+
input: {
|
|
210
|
+
...routeState.actionInput,
|
|
211
|
+
patch: request.input.body
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
reply.code(200).send(response);
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false } = {}) {
|
|
221
|
+
const routeBase = resolveAssistantApiBasePath({
|
|
222
|
+
requiresWorkspace
|
|
223
|
+
});
|
|
224
|
+
const visibility = requiresWorkspace ? "workspace" : "public";
|
|
225
|
+
const surfaceRouteBase = `${routeBase}/:surfaceId`;
|
|
226
|
+
const paramsValidator = buildRouteParamsValidator(requiresWorkspace);
|
|
227
|
+
|
|
228
|
+
router.register(
|
|
229
|
+
"POST",
|
|
230
|
+
`${surfaceRouteBase}/chat/stream`,
|
|
231
|
+
{
|
|
232
|
+
auth: "required",
|
|
233
|
+
visibility,
|
|
234
|
+
paramsValidator,
|
|
235
|
+
meta: {
|
|
236
|
+
tags: ["assistant"],
|
|
237
|
+
summary: "Stream assistant response."
|
|
238
|
+
},
|
|
239
|
+
bodyValidator: assistantResource.operations.chatStream.bodyValidator
|
|
240
|
+
},
|
|
241
|
+
async function assistantChatStreamRoute(request, reply) {
|
|
242
|
+
const routeState = resolveRouteRequestState(request, {
|
|
243
|
+
resolveCurrentAppConfig,
|
|
244
|
+
kind: "runtime",
|
|
245
|
+
requiresWorkspace
|
|
246
|
+
});
|
|
247
|
+
const abortController = new AbortController();
|
|
248
|
+
const requestBody = request?.input?.body && typeof request.input.body === "object" ? request.input.body : {};
|
|
249
|
+
const closeListener = () => {
|
|
250
|
+
abortController.abort();
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
let streamStarted = false;
|
|
254
|
+
|
|
255
|
+
function ensureStreamStarted() {
|
|
256
|
+
if (streamStarted) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
setNdjsonHeaders(reply);
|
|
261
|
+
reply.code(200);
|
|
262
|
+
reply.hijack();
|
|
263
|
+
if (typeof reply.raw.flushHeaders === "function") {
|
|
264
|
+
reply.raw.flushHeaders();
|
|
265
|
+
}
|
|
266
|
+
streamStarted = true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const streamWriter = Object.freeze({
|
|
270
|
+
sendMeta(payload = {}) {
|
|
271
|
+
ensureStreamStarted();
|
|
272
|
+
writeNdjson(reply, payload);
|
|
273
|
+
},
|
|
274
|
+
sendAssistantDelta(payload = {}) {
|
|
275
|
+
ensureStreamStarted();
|
|
276
|
+
writeNdjson(reply, payload);
|
|
277
|
+
},
|
|
278
|
+
sendAssistantMessage(payload = {}) {
|
|
279
|
+
ensureStreamStarted();
|
|
280
|
+
writeNdjson(reply, payload);
|
|
281
|
+
},
|
|
282
|
+
sendToolCall(payload = {}) {
|
|
283
|
+
ensureStreamStarted();
|
|
284
|
+
writeNdjson(reply, payload);
|
|
285
|
+
},
|
|
286
|
+
sendToolResult(payload = {}) {
|
|
287
|
+
ensureStreamStarted();
|
|
288
|
+
writeNdjson(reply, payload);
|
|
289
|
+
},
|
|
290
|
+
sendError(payload = {}) {
|
|
291
|
+
ensureStreamStarted();
|
|
292
|
+
writeNdjson(reply, payload);
|
|
293
|
+
},
|
|
294
|
+
sendDone(payload = {}) {
|
|
295
|
+
ensureStreamStarted();
|
|
296
|
+
writeNdjson(reply, payload);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
request.raw.on("close", closeListener);
|
|
302
|
+
|
|
303
|
+
await request.executeAction({
|
|
304
|
+
actionId: actionIds.chatStream,
|
|
305
|
+
context: {
|
|
306
|
+
surface: routeState.hostSurfaceId
|
|
307
|
+
},
|
|
308
|
+
input: {
|
|
309
|
+
...routeState.actionInput,
|
|
310
|
+
messageId: requestBody.messageId,
|
|
311
|
+
conversationId: requestBody.conversationId,
|
|
312
|
+
input: requestBody.input,
|
|
313
|
+
history: requestBody.history,
|
|
314
|
+
clientContext: requestBody.clientContext
|
|
315
|
+
},
|
|
316
|
+
deps: {
|
|
317
|
+
streamWriter,
|
|
318
|
+
abortSignal: abortController.signal
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
if (streamStarted) {
|
|
323
|
+
endNdjson(reply);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
reply.code(204).send();
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (!streamStarted) {
|
|
330
|
+
sendPreStreamErrorResponse(reply, error);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const streamError = mapStreamError(error);
|
|
335
|
+
writeNdjson(reply, {
|
|
336
|
+
type: "error",
|
|
337
|
+
...streamError
|
|
338
|
+
});
|
|
339
|
+
writeNdjson(reply, {
|
|
340
|
+
type: "done",
|
|
341
|
+
status: "failed"
|
|
342
|
+
});
|
|
343
|
+
endNdjson(reply);
|
|
344
|
+
} finally {
|
|
345
|
+
request.raw.off("close", closeListener);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
router.register(
|
|
351
|
+
"GET",
|
|
352
|
+
`${surfaceRouteBase}/conversations`,
|
|
353
|
+
{
|
|
354
|
+
auth: "required",
|
|
355
|
+
visibility,
|
|
356
|
+
paramsValidator,
|
|
357
|
+
meta: {
|
|
358
|
+
tags: ["assistant"],
|
|
359
|
+
summary: "List assistant conversations."
|
|
360
|
+
},
|
|
361
|
+
queryValidator: assistantResource.operations.conversationsList.queryValidator,
|
|
362
|
+
responseValidators: withStandardErrorResponses({
|
|
363
|
+
200: assistantResource.operations.conversationsList.outputValidator
|
|
364
|
+
})
|
|
365
|
+
},
|
|
366
|
+
async function assistantConversationsRoute(request, reply) {
|
|
367
|
+
const routeState = resolveRouteRequestState(request, {
|
|
368
|
+
resolveCurrentAppConfig,
|
|
369
|
+
kind: "runtime",
|
|
370
|
+
requiresWorkspace
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const response = await request.executeAction({
|
|
374
|
+
actionId: actionIds.conversationsList,
|
|
375
|
+
context: {
|
|
376
|
+
surface: routeState.hostSurfaceId
|
|
377
|
+
},
|
|
378
|
+
input: {
|
|
379
|
+
...routeState.actionInput,
|
|
380
|
+
query: request.input.query
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
reply.code(200).send(response);
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
router.register(
|
|
389
|
+
"GET",
|
|
390
|
+
`${surfaceRouteBase}/conversations/:conversationId/messages`,
|
|
391
|
+
{
|
|
392
|
+
auth: "required",
|
|
393
|
+
visibility,
|
|
394
|
+
paramsValidator: buildConversationMessagesRouteParamsValidator(requiresWorkspace),
|
|
395
|
+
meta: {
|
|
396
|
+
tags: ["assistant"],
|
|
397
|
+
summary: "List assistant conversation messages."
|
|
398
|
+
},
|
|
399
|
+
queryValidator: assistantResource.operations.conversationMessagesList.queryValidator,
|
|
400
|
+
responseValidators: withStandardErrorResponses({
|
|
401
|
+
200: assistantResource.operations.conversationMessagesList.outputValidator
|
|
402
|
+
})
|
|
403
|
+
},
|
|
404
|
+
async function assistantConversationMessagesRoute(request, reply) {
|
|
405
|
+
const routeState = resolveRouteRequestState(request, {
|
|
406
|
+
resolveCurrentAppConfig,
|
|
407
|
+
kind: "runtime",
|
|
408
|
+
requiresWorkspace
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const response = await request.executeAction({
|
|
412
|
+
actionId: actionIds.conversationMessagesList,
|
|
413
|
+
context: {
|
|
414
|
+
surface: routeState.hostSurfaceId
|
|
415
|
+
},
|
|
416
|
+
input: {
|
|
417
|
+
...routeState.actionInput,
|
|
418
|
+
conversationId: request.input.params.conversationId,
|
|
419
|
+
query: request.input.query
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
reply.code(200).send(response);
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function registerRoutes(app) {
|
|
429
|
+
if (!app || typeof app.make !== "function") {
|
|
430
|
+
throw new Error("registerRoutes requires application make().");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const router = app.make("jskit.http.router");
|
|
434
|
+
const resolveCurrentAppConfig = () => resolveAppConfig(app);
|
|
435
|
+
|
|
436
|
+
registerSettingsRoutes(router, resolveCurrentAppConfig, {
|
|
437
|
+
requiresWorkspace: false
|
|
438
|
+
});
|
|
439
|
+
registerSettingsRoutes(router, resolveCurrentAppConfig, {
|
|
440
|
+
requiresWorkspace: true
|
|
441
|
+
});
|
|
442
|
+
registerRuntimeRoutes(router, resolveCurrentAppConfig, {
|
|
443
|
+
requiresWorkspace: false
|
|
444
|
+
});
|
|
445
|
+
registerRuntimeRoutes(router, resolveCurrentAppConfig, {
|
|
446
|
+
requiresWorkspace: true
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export { registerRoutes };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
3
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
import { resolveInsertedId } from "@jskit-ai/assistant-core/server";
|
|
5
|
+
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
6
|
+
|
|
7
|
+
function normalizeTargetSurfaceId(value = "") {
|
|
8
|
+
return normalizeSurfaceId(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeWorkspaceId(value) {
|
|
12
|
+
return parsePositiveInteger(value) || null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildScopeKey(targetSurfaceId, workspaceId = null) {
|
|
16
|
+
const normalizedTargetSurfaceId = normalizeTargetSurfaceId(targetSurfaceId);
|
|
17
|
+
if (!normalizedTargetSurfaceId) {
|
|
18
|
+
throw new TypeError("assistantConfigRepository requires targetSurfaceId.");
|
|
19
|
+
}
|
|
20
|
+
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
|
|
21
|
+
if (normalizedWorkspaceId) {
|
|
22
|
+
return `${normalizedTargetSurfaceId}:workspace:${normalizedWorkspaceId}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return `${normalizedTargetSurfaceId}:global`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function mapConfigRow(row = {}) {
|
|
29
|
+
return {
|
|
30
|
+
targetSurfaceId: normalizeTargetSurfaceId(row.target_surface_id),
|
|
31
|
+
scopeKey: normalizeText(row.scope_key),
|
|
32
|
+
workspaceId: normalizeWorkspaceId(row.workspace_id),
|
|
33
|
+
settings: {
|
|
34
|
+
systemPrompt: String(row.system_prompt || "")
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createDefaultRecord({ targetSurfaceId = "", workspaceId = null } = {}) {
|
|
40
|
+
const normalizedTargetSurfaceId = normalizeTargetSurfaceId(targetSurfaceId);
|
|
41
|
+
if (!normalizedTargetSurfaceId) {
|
|
42
|
+
throw new TypeError("assistantConfigRepository requires targetSurfaceId.");
|
|
43
|
+
}
|
|
44
|
+
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
targetSurfaceId: normalizedTargetSurfaceId,
|
|
48
|
+
scopeKey: buildScopeKey(normalizedTargetSurfaceId, normalizedWorkspaceId),
|
|
49
|
+
workspaceId: normalizedWorkspaceId,
|
|
50
|
+
settings: {
|
|
51
|
+
systemPrompt: ""
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createRepository(knex) {
|
|
57
|
+
if (!knex || typeof knex !== "function") {
|
|
58
|
+
throw new Error("createAssistantConfigRepository requires knex client.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function findByScope({ targetSurfaceId = "", workspaceId = null } = {}, options = {}) {
|
|
62
|
+
const client = options?.trx || knex;
|
|
63
|
+
const defaultRecord = createDefaultRecord({
|
|
64
|
+
targetSurfaceId,
|
|
65
|
+
workspaceId
|
|
66
|
+
});
|
|
67
|
+
const row = await client(assistantRuntimeConfig.configTable)
|
|
68
|
+
.where({
|
|
69
|
+
target_surface_id: defaultRecord.targetSurfaceId,
|
|
70
|
+
scope_key: defaultRecord.scopeKey
|
|
71
|
+
})
|
|
72
|
+
.first();
|
|
73
|
+
|
|
74
|
+
return row ? mapConfigRow(row) : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function upsertByScope({ targetSurfaceId = "", workspaceId = null, patch = {} } = {}, options = {}) {
|
|
78
|
+
const client = options?.trx || knex;
|
|
79
|
+
const defaultRecord = createDefaultRecord({
|
|
80
|
+
targetSurfaceId,
|
|
81
|
+
workspaceId
|
|
82
|
+
});
|
|
83
|
+
const existing = await findByScope(defaultRecord, {
|
|
84
|
+
trx: client
|
|
85
|
+
});
|
|
86
|
+
const nextSystemPrompt = Object.hasOwn(patch || {}, "systemPrompt")
|
|
87
|
+
? String(patch.systemPrompt || "")
|
|
88
|
+
: String(existing?.settings?.systemPrompt || defaultRecord.settings.systemPrompt);
|
|
89
|
+
const now = new Date();
|
|
90
|
+
|
|
91
|
+
if (existing) {
|
|
92
|
+
await client(assistantRuntimeConfig.configTable)
|
|
93
|
+
.where({
|
|
94
|
+
target_surface_id: defaultRecord.targetSurfaceId,
|
|
95
|
+
scope_key: defaultRecord.scopeKey
|
|
96
|
+
})
|
|
97
|
+
.update({
|
|
98
|
+
workspace_id: defaultRecord.workspaceId,
|
|
99
|
+
system_prompt: nextSystemPrompt,
|
|
100
|
+
updated_at: now
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
...defaultRecord,
|
|
105
|
+
settings: {
|
|
106
|
+
systemPrompt: nextSystemPrompt
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const insertResult = await client(assistantRuntimeConfig.configTable).insert({
|
|
112
|
+
target_surface_id: defaultRecord.targetSurfaceId,
|
|
113
|
+
scope_key: defaultRecord.scopeKey,
|
|
114
|
+
workspace_id: defaultRecord.workspaceId,
|
|
115
|
+
system_prompt: nextSystemPrompt,
|
|
116
|
+
created_at: now,
|
|
117
|
+
updated_at: now
|
|
118
|
+
});
|
|
119
|
+
const insertedId = resolveInsertedId(insertResult);
|
|
120
|
+
if (!insertedId) {
|
|
121
|
+
return {
|
|
122
|
+
...defaultRecord,
|
|
123
|
+
settings: {
|
|
124
|
+
systemPrompt: nextSystemPrompt
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const insertedRow = await client(assistantRuntimeConfig.configTable)
|
|
130
|
+
.where({ id: insertedId })
|
|
131
|
+
.first();
|
|
132
|
+
|
|
133
|
+
return insertedRow ? mapConfigRow(insertedRow) : {
|
|
134
|
+
...defaultRecord,
|
|
135
|
+
settings: {
|
|
136
|
+
systemPrompt: nextSystemPrompt
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
createDefaultRecord,
|
|
143
|
+
findByScope,
|
|
144
|
+
upsertByScope
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { createRepository };
|