@jskit-ai/assistant-runtime 0.1.26 → 0.1.28
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 +8 -8
- package/package.json +12 -8
- package/src/client/components/AssistantSettingsClientElement.vue +1 -1
- package/src/client/composables/useAssistantRuntime.js +19 -1
- package/src/client/support/composerInputSupport.js +29 -0
- package/src/server/actions.js +81 -27
- package/src/server/inputSchemas.js +44 -0
- package/src/server/registerRoutes.js +61 -45
- package/src/server/support/workspaceScopeSupport.js +14 -2
- package/test/actionDefinitions.test.js +192 -0
- package/test/assistantServerConfig.test.js +4 -4
- package/test/composerInputSupport.test.js +23 -0
- package/test/lazyAppConfig.test.js +75 -83
- package/src/server/inputValidators.js +0 -41
package/package.descriptor.mjs
CHANGED
|
@@ -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.
|
|
4
|
+
version: "0.1.28",
|
|
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.
|
|
78
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
79
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
80
|
-
"@jskit-ai/kernel": "0.1.
|
|
81
|
-
"@jskit-ai/shell-web": "0.1.
|
|
82
|
-
"@jskit-ai/users-core": "0.1.
|
|
83
|
-
"@jskit-ai/users-web": "0.1.
|
|
77
|
+
"@jskit-ai/assistant-core": "0.1.33",
|
|
78
|
+
"@jskit-ai/database-runtime": "0.1.57",
|
|
79
|
+
"@jskit-ai/http-runtime": "0.1.56",
|
|
80
|
+
"@jskit-ai/kernel": "0.1.57",
|
|
81
|
+
"@jskit-ai/shell-web": "0.1.56",
|
|
82
|
+
"@jskit-ai/users-core": "0.1.67",
|
|
83
|
+
"@jskit-ai/users-web": "0.1.72",
|
|
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.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./client": "./src/client/index.js",
|
|
@@ -8,14 +8,18 @@
|
|
|
8
8
|
"./server/actionIds": "./src/server/actionIds.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
12
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
13
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
14
|
-
"@jskit-ai/kernel": "0.1.
|
|
15
|
-
"@jskit-ai/shell-web": "0.1.
|
|
16
|
-
"@jskit-ai/users-core": "0.1.
|
|
17
|
-
"@jskit-ai/users-web": "0.1.
|
|
11
|
+
"@jskit-ai/assistant-core": "0.1.33",
|
|
12
|
+
"@jskit-ai/database-runtime": "0.1.57",
|
|
13
|
+
"@jskit-ai/http-runtime": "0.1.56",
|
|
14
|
+
"@jskit-ai/kernel": "0.1.57",
|
|
15
|
+
"@jskit-ai/shell-web": "0.1.56",
|
|
16
|
+
"@jskit-ai/users-core": "0.1.67",
|
|
17
|
+
"@jskit-ai/users-web": "0.1.72",
|
|
18
|
+
"json-rest-schema": "1.x.x",
|
|
18
19
|
"@tanstack/vue-query": "^5.90.5",
|
|
19
20
|
"vuetify": "^4.0.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"vue": "^3.5.13"
|
|
20
24
|
}
|
|
21
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, ref, watch } from "vue";
|
|
1
|
+
import { computed, nextTick, ref, watch } from "vue";
|
|
2
2
|
import { useQueryClient } from "@tanstack/vue-query";
|
|
3
3
|
import { getClientAppConfig } from "@jskit-ai/kernel/client";
|
|
4
4
|
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
@@ -23,6 +23,7 @@ import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
|
|
|
23
23
|
import { usePagedCollection } from "@jskit-ai/users-web/client/composables/usePagedCollection";
|
|
24
24
|
import { useSurfaceRouteContext } from "@jskit-ai/users-web/client/composables/useSurfaceRouteContext";
|
|
25
25
|
import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
|
|
26
|
+
import { insertTextAtSelection } from "../support/composerInputSupport.js";
|
|
26
27
|
import { useWorkspaceWebScopeSupport } from "../support/workspaceScopeSupport.js";
|
|
27
28
|
|
|
28
29
|
const DEFAULT_STREAM_TIMEOUT_MS = 120_000;
|
|
@@ -523,6 +524,23 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
523
524
|
return;
|
|
524
525
|
}
|
|
525
526
|
|
|
527
|
+
if (event?.key === "Enter" && event?.altKey === true && event?.ctrlKey !== true && event?.metaKey !== true) {
|
|
528
|
+
event.preventDefault();
|
|
529
|
+
|
|
530
|
+
const target = event?.target;
|
|
531
|
+
const nextValue = insertTextAtSelection(input.value, target?.selectionStart, target?.selectionEnd, "\n");
|
|
532
|
+
input.value = nextValue.value;
|
|
533
|
+
|
|
534
|
+
void nextTick(() => {
|
|
535
|
+
if (!target || typeof target.setSelectionRange !== "function") {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
target.setSelectionRange(nextValue.selectionStart, nextValue.selectionEnd);
|
|
540
|
+
});
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
526
544
|
if (
|
|
527
545
|
event?.key === "Enter" &&
|
|
528
546
|
event?.shiftKey !== true &&
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function normalizeSelectionBoundary(value, fallback, max) {
|
|
2
|
+
const parsed = Number(value);
|
|
3
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
4
|
+
return fallback;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (parsed > max) {
|
|
8
|
+
return max;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return parsed;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function insertTextAtSelection(source = "", selectionStart, selectionEnd, text = "") {
|
|
15
|
+
const value = String(source || "");
|
|
16
|
+
const maxBoundary = value.length;
|
|
17
|
+
const normalizedStart = normalizeSelectionBoundary(selectionStart, maxBoundary, maxBoundary);
|
|
18
|
+
const normalizedEnd = normalizeSelectionBoundary(selectionEnd, normalizedStart, maxBoundary);
|
|
19
|
+
const insertedText = String(text ?? "");
|
|
20
|
+
const nextBoundary = normalizedStart + insertedText.length;
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
value: `${value.slice(0, normalizedStart)}${insertedText}${value.slice(normalizedEnd)}`,
|
|
24
|
+
selectionStart: nextBoundary,
|
|
25
|
+
selectionEnd: nextBoundary
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { insertTextAtSelection };
|
package/src/server/actions.js
CHANGED
|
@@ -1,36 +1,90 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import {
|
|
3
|
+
composeSchemaDefinitions
|
|
4
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
5
|
+
import {
|
|
6
|
+
deepFreeze
|
|
7
|
+
} from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
1
8
|
import {
|
|
2
9
|
assistantConfigResource,
|
|
3
10
|
assistantResource
|
|
4
11
|
} from "@jskit-ai/assistant-core/shared";
|
|
5
12
|
import { actionIds } from "./actionIds.js";
|
|
6
|
-
import { assistantTargetSurfaceInputValidator } from "./
|
|
13
|
+
import { assistantTargetSurfaceInputValidator } from "./inputSchemas.js";
|
|
14
|
+
|
|
15
|
+
const runtimeConversationsListQueryInputValidator = deepFreeze({
|
|
16
|
+
schema: createSchema({
|
|
17
|
+
query: {
|
|
18
|
+
type: "object",
|
|
19
|
+
required: false,
|
|
20
|
+
schema: assistantResource.operations.conversationsList.query.schema
|
|
21
|
+
}
|
|
22
|
+
}),
|
|
23
|
+
mode: "patch"
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const runtimeConversationsListInputValidator = composeSchemaDefinitions(
|
|
27
|
+
[assistantTargetSurfaceInputValidator, runtimeConversationsListQueryInputValidator],
|
|
28
|
+
{
|
|
29
|
+
mode: "patch",
|
|
30
|
+
context: "assistant-runtime conversations list action input"
|
|
31
|
+
}
|
|
32
|
+
);
|
|
7
33
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
34
|
+
const runtimeConversationMessagesListQueryInputValidator = deepFreeze({
|
|
35
|
+
schema: createSchema({
|
|
36
|
+
query: {
|
|
37
|
+
type: "object",
|
|
38
|
+
required: false,
|
|
39
|
+
schema: assistantResource.operations.conversationMessagesList.query.schema
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
mode: "patch"
|
|
43
|
+
});
|
|
12
44
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
45
|
+
const runtimeConversationMessagesListInputValidator = composeSchemaDefinitions(
|
|
46
|
+
[
|
|
47
|
+
assistantTargetSurfaceInputValidator,
|
|
48
|
+
assistantResource.operations.conversationMessagesList.params,
|
|
49
|
+
runtimeConversationMessagesListQueryInputValidator
|
|
50
|
+
],
|
|
16
51
|
{
|
|
17
|
-
|
|
52
|
+
mode: "patch",
|
|
53
|
+
context: "assistant-runtime conversation messages action input"
|
|
18
54
|
}
|
|
19
|
-
|
|
55
|
+
);
|
|
20
56
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
57
|
+
const runtimeChatStreamInputValidator = composeSchemaDefinitions(
|
|
58
|
+
[
|
|
59
|
+
assistantTargetSurfaceInputValidator,
|
|
60
|
+
assistantResource.operations.chatStream.body
|
|
61
|
+
],
|
|
62
|
+
{
|
|
63
|
+
mode: "patch",
|
|
64
|
+
context: "assistant-runtime chat stream action input"
|
|
65
|
+
}
|
|
66
|
+
);
|
|
25
67
|
|
|
26
68
|
const settingsReadInputValidator = assistantTargetSurfaceInputValidator;
|
|
27
69
|
|
|
28
|
-
const
|
|
29
|
-
|
|
70
|
+
const settingsUpdatePatchInputValidator = deepFreeze({
|
|
71
|
+
schema: createSchema({
|
|
72
|
+
patch: {
|
|
73
|
+
type: "object",
|
|
74
|
+
required: true,
|
|
75
|
+
schema: assistantConfigResource.operations.patch.body.schema
|
|
76
|
+
}
|
|
77
|
+
}),
|
|
78
|
+
mode: "patch"
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const settingsUpdateInputValidator = composeSchemaDefinitions(
|
|
82
|
+
[assistantTargetSurfaceInputValidator, settingsUpdatePatchInputValidator],
|
|
30
83
|
{
|
|
31
|
-
|
|
84
|
+
mode: "patch",
|
|
85
|
+
context: "assistant-runtime settings update action input"
|
|
32
86
|
}
|
|
33
|
-
|
|
87
|
+
);
|
|
34
88
|
|
|
35
89
|
const assistantActions = Object.freeze([
|
|
36
90
|
{
|
|
@@ -42,7 +96,7 @@ const assistantActions = Object.freeze([
|
|
|
42
96
|
permission: {
|
|
43
97
|
require: "authenticated"
|
|
44
98
|
},
|
|
45
|
-
|
|
99
|
+
input: runtimeChatStreamInputValidator,
|
|
46
100
|
idempotency: "optional",
|
|
47
101
|
audit: {
|
|
48
102
|
actionName: actionIds.chatStream
|
|
@@ -65,8 +119,8 @@ const assistantActions = Object.freeze([
|
|
|
65
119
|
permission: {
|
|
66
120
|
require: "authenticated"
|
|
67
121
|
},
|
|
68
|
-
|
|
69
|
-
|
|
122
|
+
input: runtimeConversationsListInputValidator,
|
|
123
|
+
output: assistantResource.operations.conversationsList.output,
|
|
70
124
|
idempotency: "none",
|
|
71
125
|
audit: {
|
|
72
126
|
actionName: actionIds.conversationsList
|
|
@@ -88,8 +142,8 @@ const assistantActions = Object.freeze([
|
|
|
88
142
|
permission: {
|
|
89
143
|
require: "authenticated"
|
|
90
144
|
},
|
|
91
|
-
|
|
92
|
-
|
|
145
|
+
input: runtimeConversationMessagesListInputValidator,
|
|
146
|
+
output: assistantResource.operations.conversationMessagesList.output,
|
|
93
147
|
idempotency: "none",
|
|
94
148
|
audit: {
|
|
95
149
|
actionName: actionIds.conversationMessagesList
|
|
@@ -111,8 +165,8 @@ const assistantActions = Object.freeze([
|
|
|
111
165
|
permission: {
|
|
112
166
|
require: "authenticated"
|
|
113
167
|
},
|
|
114
|
-
|
|
115
|
-
|
|
168
|
+
input: settingsReadInputValidator,
|
|
169
|
+
output: assistantConfigResource.operations.view.output,
|
|
116
170
|
idempotency: "none",
|
|
117
171
|
audit: {
|
|
118
172
|
actionName: actionIds.settingsRead
|
|
@@ -133,8 +187,8 @@ const assistantActions = Object.freeze([
|
|
|
133
187
|
permission: {
|
|
134
188
|
require: "authenticated"
|
|
135
189
|
},
|
|
136
|
-
|
|
137
|
-
|
|
190
|
+
input: settingsUpdateInputValidator,
|
|
191
|
+
output: assistantConfigResource.operations.patch.output,
|
|
138
192
|
idempotency: "optional",
|
|
139
193
|
audit: {
|
|
140
194
|
actionName: actionIds.settingsUpdate
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
3
|
+
|
|
4
|
+
const assistantSurfaceRouteParamsSchema = createSchema({
|
|
5
|
+
surfaceId: {
|
|
6
|
+
type: "string",
|
|
7
|
+
required: true,
|
|
8
|
+
lowercase: true,
|
|
9
|
+
minLength: 1,
|
|
10
|
+
maxLength: 64
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const assistantTargetSurfaceInputSchema = createSchema({
|
|
15
|
+
targetSurfaceId: {
|
|
16
|
+
type: "string",
|
|
17
|
+
required: true,
|
|
18
|
+
lowercase: true,
|
|
19
|
+
minLength: 1,
|
|
20
|
+
maxLength: 64
|
|
21
|
+
},
|
|
22
|
+
workspaceSlug: {
|
|
23
|
+
type: "string",
|
|
24
|
+
required: false,
|
|
25
|
+
lowercase: true,
|
|
26
|
+
minLength: 1,
|
|
27
|
+
maxLength: 160
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const assistantSurfaceRouteParamsValidator = deepFreeze({
|
|
32
|
+
schema: assistantSurfaceRouteParamsSchema,
|
|
33
|
+
mode: "patch"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const assistantTargetSurfaceInputValidator = deepFreeze({
|
|
37
|
+
schema: assistantTargetSurfaceInputSchema,
|
|
38
|
+
mode: "patch"
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
assistantSurfaceRouteParamsValidator,
|
|
43
|
+
assistantTargetSurfaceInputValidator
|
|
44
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
2
2
|
import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
|
|
3
|
+
import { composeSchemaDefinitions } from "@jskit-ai/kernel/shared/validators";
|
|
3
4
|
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
4
5
|
import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
|
|
5
6
|
import {
|
|
@@ -15,28 +16,21 @@ import {
|
|
|
15
16
|
} from "@jskit-ai/assistant-core/server";
|
|
16
17
|
import { resolveAssistantSurfaceConfig } from "../shared/assistantSurfaces.js";
|
|
17
18
|
import { actionIds } from "./actionIds.js";
|
|
18
|
-
import { assistantSurfaceRouteParamsValidator } from "./
|
|
19
|
+
import { assistantSurfaceRouteParamsValidator } from "./inputSchemas.js";
|
|
19
20
|
import { resolveWorkspaceServerScopeSupport } from "./support/workspaceScopeSupport.js";
|
|
20
21
|
|
|
21
|
-
function
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
throw new Error("Assistant workspace routes require workspace server scope support.");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return [workspaceScopeSupport.paramsValidator, assistantSurfaceRouteParamsValidator];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return assistantSurfaceRouteParamsValidator;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function buildConversationMessagesRouteParamsValidator(requiresWorkspace, workspaceScopeSupport = null) {
|
|
34
|
-
const validators = buildRouteParamsValidator(requiresWorkspace, workspaceScopeSupport);
|
|
35
|
-
if (Array.isArray(validators)) {
|
|
36
|
-
return validators.concat(assistantResource.operations.conversationMessagesList.paramsValidator);
|
|
22
|
+
function requireWorkspaceAssistantRouteParams(workspaceScopeSupport = null) {
|
|
23
|
+
if (!workspaceScopeSupport) {
|
|
24
|
+
throw new Error("Assistant workspace routes require workspace server scope support.");
|
|
37
25
|
}
|
|
38
26
|
|
|
39
|
-
return
|
|
27
|
+
return composeSchemaDefinitions(
|
|
28
|
+
[workspaceScopeSupport.params, assistantSurfaceRouteParamsValidator],
|
|
29
|
+
{
|
|
30
|
+
mode: "patch",
|
|
31
|
+
context: "assistant-runtime workspace surface route params"
|
|
32
|
+
}
|
|
33
|
+
);
|
|
40
34
|
}
|
|
41
35
|
|
|
42
36
|
function readWorkspaceInput(request, requiresWorkspace, workspaceScopeSupport = null) {
|
|
@@ -146,6 +140,22 @@ function resolveRouteRequestState(
|
|
|
146
140
|
});
|
|
147
141
|
}
|
|
148
142
|
|
|
143
|
+
function buildChatStreamActionInput(routeInput = {}, requestBody = {}) {
|
|
144
|
+
const actionInput = {
|
|
145
|
+
...routeInput,
|
|
146
|
+
messageId: requestBody.messageId,
|
|
147
|
+
input: requestBody.input
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
for (const key of ["conversationId", "history", "clientContext"]) {
|
|
151
|
+
if (Object.prototype.hasOwnProperty.call(requestBody, key)) {
|
|
152
|
+
actionInput[key] = requestBody[key];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return actionInput;
|
|
157
|
+
}
|
|
158
|
+
|
|
149
159
|
function registerSettingsRoutes(
|
|
150
160
|
router,
|
|
151
161
|
resolveCurrentAppConfig,
|
|
@@ -156,7 +166,9 @@ function registerSettingsRoutes(
|
|
|
156
166
|
});
|
|
157
167
|
const visibility = requiresWorkspace ? "workspace" : "public";
|
|
158
168
|
const routePath = `${routeBase}/:surfaceId/settings`;
|
|
159
|
-
const
|
|
169
|
+
const params = requiresWorkspace === true
|
|
170
|
+
? requireWorkspaceAssistantRouteParams(workspaceScopeSupport)
|
|
171
|
+
: assistantSurfaceRouteParamsValidator;
|
|
160
172
|
|
|
161
173
|
router.register(
|
|
162
174
|
"GET",
|
|
@@ -164,13 +176,13 @@ function registerSettingsRoutes(
|
|
|
164
176
|
{
|
|
165
177
|
auth: "required",
|
|
166
178
|
visibility,
|
|
167
|
-
|
|
179
|
+
params,
|
|
168
180
|
meta: {
|
|
169
181
|
tags: ["assistant", "settings"],
|
|
170
182
|
summary: "Get assistant settings."
|
|
171
183
|
},
|
|
172
|
-
|
|
173
|
-
200: assistantConfigResource.operations.view.
|
|
184
|
+
responses: withStandardErrorResponses({
|
|
185
|
+
200: assistantConfigResource.operations.view.output
|
|
174
186
|
})
|
|
175
187
|
},
|
|
176
188
|
async function assistantSettingsReadRoute(request, reply) {
|
|
@@ -199,15 +211,15 @@ function registerSettingsRoutes(
|
|
|
199
211
|
{
|
|
200
212
|
auth: "required",
|
|
201
213
|
visibility,
|
|
202
|
-
|
|
214
|
+
params,
|
|
203
215
|
meta: {
|
|
204
216
|
tags: ["assistant", "settings"],
|
|
205
217
|
summary: "Update assistant settings."
|
|
206
218
|
},
|
|
207
|
-
|
|
208
|
-
|
|
219
|
+
body: assistantConfigResource.operations.patch.body,
|
|
220
|
+
responses: withStandardErrorResponses(
|
|
209
221
|
{
|
|
210
|
-
200: assistantConfigResource.operations.patch.
|
|
222
|
+
200: assistantConfigResource.operations.patch.output
|
|
211
223
|
},
|
|
212
224
|
{
|
|
213
225
|
includeValidation400: true
|
|
@@ -248,7 +260,18 @@ function registerRuntimeRoutes(
|
|
|
248
260
|
});
|
|
249
261
|
const visibility = requiresWorkspace ? "workspace" : "public";
|
|
250
262
|
const surfaceRouteBase = `${routeBase}/:surfaceId`;
|
|
251
|
-
const
|
|
263
|
+
const params = requiresWorkspace === true
|
|
264
|
+
? requireWorkspaceAssistantRouteParams(workspaceScopeSupport)
|
|
265
|
+
: assistantSurfaceRouteParamsValidator;
|
|
266
|
+
const conversationMessagesParams = composeSchemaDefinitions(
|
|
267
|
+
[params, assistantResource.operations.conversationMessagesList.params],
|
|
268
|
+
{
|
|
269
|
+
mode: "patch",
|
|
270
|
+
context: requiresWorkspace === true
|
|
271
|
+
? "assistant-runtime workspace conversation messages route params"
|
|
272
|
+
: "assistant-runtime conversation messages route params"
|
|
273
|
+
}
|
|
274
|
+
);
|
|
252
275
|
|
|
253
276
|
router.register(
|
|
254
277
|
"POST",
|
|
@@ -256,12 +279,12 @@ function registerRuntimeRoutes(
|
|
|
256
279
|
{
|
|
257
280
|
auth: "required",
|
|
258
281
|
visibility,
|
|
259
|
-
|
|
282
|
+
params,
|
|
260
283
|
meta: {
|
|
261
284
|
tags: ["assistant"],
|
|
262
285
|
summary: "Stream assistant response."
|
|
263
286
|
},
|
|
264
|
-
|
|
287
|
+
body: assistantResource.operations.chatStream.body
|
|
265
288
|
},
|
|
266
289
|
async function assistantChatStreamRoute(request, reply) {
|
|
267
290
|
const routeState = resolveRouteRequestState(request, {
|
|
@@ -331,14 +354,7 @@ function registerRuntimeRoutes(
|
|
|
331
354
|
context: {
|
|
332
355
|
surface: routeState.hostSurfaceId
|
|
333
356
|
},
|
|
334
|
-
input:
|
|
335
|
-
...routeState.actionInput,
|
|
336
|
-
messageId: requestBody.messageId,
|
|
337
|
-
conversationId: requestBody.conversationId,
|
|
338
|
-
input: requestBody.input,
|
|
339
|
-
history: requestBody.history,
|
|
340
|
-
clientContext: requestBody.clientContext
|
|
341
|
-
},
|
|
357
|
+
input: buildChatStreamActionInput(routeState.actionInput, requestBody),
|
|
342
358
|
deps: {
|
|
343
359
|
streamWriter,
|
|
344
360
|
abortSignal: abortController.signal
|
|
@@ -379,14 +395,14 @@ function registerRuntimeRoutes(
|
|
|
379
395
|
{
|
|
380
396
|
auth: "required",
|
|
381
397
|
visibility,
|
|
382
|
-
|
|
398
|
+
params,
|
|
383
399
|
meta: {
|
|
384
400
|
tags: ["assistant"],
|
|
385
401
|
summary: "List assistant conversations."
|
|
386
402
|
},
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
200: assistantResource.operations.conversationsList.
|
|
403
|
+
query: assistantResource.operations.conversationsList.query,
|
|
404
|
+
responses: withStandardErrorResponses({
|
|
405
|
+
200: assistantResource.operations.conversationsList.output
|
|
390
406
|
})
|
|
391
407
|
},
|
|
392
408
|
async function assistantConversationsRoute(request, reply) {
|
|
@@ -418,14 +434,14 @@ function registerRuntimeRoutes(
|
|
|
418
434
|
{
|
|
419
435
|
auth: "required",
|
|
420
436
|
visibility,
|
|
421
|
-
|
|
437
|
+
params: conversationMessagesParams,
|
|
422
438
|
meta: {
|
|
423
439
|
tags: ["assistant"],
|
|
424
440
|
summary: "List assistant conversation messages."
|
|
425
441
|
},
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
200: assistantResource.operations.conversationMessagesList.
|
|
442
|
+
query: assistantResource.operations.conversationMessagesList.query,
|
|
443
|
+
responses: withStandardErrorResponses({
|
|
444
|
+
200: assistantResource.operations.conversationMessagesList.output
|
|
429
445
|
})
|
|
430
446
|
},
|
|
431
447
|
async function assistantConversationMessagesRoute(request, reply) {
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
+
import { normalizeSchemaDefinition } from "@jskit-ai/kernel/shared/validators";
|
|
2
|
+
|
|
1
3
|
const WORKSPACES_SERVER_SCOPE_SUPPORT_TOKEN = "workspaces.server.scope-support";
|
|
2
4
|
|
|
5
|
+
function hasWorkspaceRouteParamsDefinition(value) {
|
|
6
|
+
try {
|
|
7
|
+
return Boolean(normalizeSchemaDefinition(value, {
|
|
8
|
+
context: "assistant-runtime workspace scope support.params",
|
|
9
|
+
defaultMode: "patch"
|
|
10
|
+
}));
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
3
16
|
function isWorkspaceServerScopeSupport(value) {
|
|
4
17
|
return Boolean(
|
|
5
18
|
value &&
|
|
6
19
|
value.available === true &&
|
|
7
|
-
value.
|
|
8
|
-
typeof value.paramsValidator.normalize === "function" &&
|
|
20
|
+
hasWorkspaceRouteParamsDefinition(value.params) &&
|
|
9
21
|
typeof value.buildInputFromRouteParams === "function" &&
|
|
10
22
|
typeof value.resolveWorkspace === "function"
|
|
11
23
|
);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
withActionDefaults
|
|
6
|
+
} from "@jskit-ai/kernel/shared/actions";
|
|
7
|
+
import { ActionRuntimeServiceProvider } from "@jskit-ai/kernel/server/actions";
|
|
8
|
+
|
|
9
|
+
import { assistantActions } from "../src/server/actions.js";
|
|
10
|
+
import { actionIds } from "../src/server/actionIds.js";
|
|
11
|
+
|
|
12
|
+
function createSingletonApp() {
|
|
13
|
+
const singletons = new Map();
|
|
14
|
+
const instances = new Map();
|
|
15
|
+
const tags = new Map();
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
has(token) {
|
|
19
|
+
return singletons.has(token) || instances.has(token);
|
|
20
|
+
},
|
|
21
|
+
singleton(token, factory) {
|
|
22
|
+
singletons.set(token, {
|
|
23
|
+
factory,
|
|
24
|
+
resolved: false,
|
|
25
|
+
value: undefined
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
tag(token, tagName) {
|
|
29
|
+
if (!this.has(token)) {
|
|
30
|
+
throw new Error(`Cannot tag unresolved token "${String(token)}".`);
|
|
31
|
+
}
|
|
32
|
+
if (!tags.has(tagName)) {
|
|
33
|
+
tags.set(tagName, new Set());
|
|
34
|
+
}
|
|
35
|
+
tags.get(tagName).add(token);
|
|
36
|
+
},
|
|
37
|
+
resolveTag(tagName) {
|
|
38
|
+
const tagged = tags.get(tagName);
|
|
39
|
+
if (!tagged) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
return [...tagged].map((token) => this.make(token));
|
|
43
|
+
},
|
|
44
|
+
make(token) {
|
|
45
|
+
if (instances.has(token)) {
|
|
46
|
+
return instances.get(token);
|
|
47
|
+
}
|
|
48
|
+
if (!singletons.has(token)) {
|
|
49
|
+
throw new Error(`Token "${String(token)}" is not registered.`);
|
|
50
|
+
}
|
|
51
|
+
const entry = singletons.get(token);
|
|
52
|
+
if (!entry.resolved) {
|
|
53
|
+
entry.value = entry.factory(this);
|
|
54
|
+
entry.resolved = true;
|
|
55
|
+
instances.set(token, entry.value);
|
|
56
|
+
}
|
|
57
|
+
return entry.value;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createAssistantActionExecutor() {
|
|
63
|
+
const app = createSingletonApp();
|
|
64
|
+
const provider = new ActionRuntimeServiceProvider();
|
|
65
|
+
provider.register(app);
|
|
66
|
+
|
|
67
|
+
app.singleton("jskit.surface.runtime", () => ({
|
|
68
|
+
listEnabledSurfaceIds() {
|
|
69
|
+
return ["home", "console"];
|
|
70
|
+
}
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
app.singleton("assistant.chat.service", () => ({
|
|
74
|
+
streamChat() {
|
|
75
|
+
return { ok: true };
|
|
76
|
+
},
|
|
77
|
+
listConversations(query, { input }) {
|
|
78
|
+
return { query, input };
|
|
79
|
+
},
|
|
80
|
+
getConversationMessages(conversationId, query, { input }) {
|
|
81
|
+
return { conversationId, query, input };
|
|
82
|
+
}
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
app.singleton("assistant.config.service", () => ({
|
|
86
|
+
getSettings(input) {
|
|
87
|
+
return { input };
|
|
88
|
+
},
|
|
89
|
+
updateSettings(input, patch) {
|
|
90
|
+
return { input, patch };
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
app.actions(
|
|
95
|
+
withActionDefaults(assistantActions, {
|
|
96
|
+
domain: "assistant",
|
|
97
|
+
dependencies: {
|
|
98
|
+
chatService: "assistant.chat.service",
|
|
99
|
+
assistantConfigService: "assistant.config.service"
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return app.make("actionExecutor");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function findDefinition(definitions, id) {
|
|
108
|
+
return definitions.find((definition) => definition.id === id);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
test("assistant-runtime actions materialize through the action runtime with single schema-definition inputs", () => {
|
|
112
|
+
const actionExecutor = createAssistantActionExecutor();
|
|
113
|
+
const definitions = actionExecutor.listDefinitions();
|
|
114
|
+
|
|
115
|
+
assert.equal(definitions.length, assistantActions.length);
|
|
116
|
+
|
|
117
|
+
for (const action of assistantActions) {
|
|
118
|
+
assert.equal(Array.isArray(action.input), false, `${action.id} input must not be an array`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const definition of definitions) {
|
|
122
|
+
assert.equal(typeof definition.input?.schema?.patch, "function", `${definition.id} input schema must normalize`);
|
|
123
|
+
assert.equal(
|
|
124
|
+
typeof definition.input?.schema?.toJsonSchema,
|
|
125
|
+
"function",
|
|
126
|
+
`${definition.id} input schema must export`
|
|
127
|
+
);
|
|
128
|
+
assert.deepEqual(definition.surfaces, ["home", "console"]);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("assistant-runtime conversations list action keeps query nested under a schema definition", () => {
|
|
133
|
+
const definition = findDefinition(createAssistantActionExecutor().listDefinitions(), actionIds.conversationsList);
|
|
134
|
+
const result = definition.input.schema.patch({
|
|
135
|
+
targetSurfaceId: "home",
|
|
136
|
+
workspaceSlug: "example-workspace",
|
|
137
|
+
query: {
|
|
138
|
+
limit: 10,
|
|
139
|
+
status: "active"
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
assert.deepEqual(result.errors, {});
|
|
144
|
+
assert.deepEqual(result.validatedObject, {
|
|
145
|
+
targetSurfaceId: "home",
|
|
146
|
+
workspaceSlug: "example-workspace",
|
|
147
|
+
query: {
|
|
148
|
+
limit: 10,
|
|
149
|
+
status: "active"
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("assistant-runtime conversation messages list action composes params and nested query", () => {
|
|
155
|
+
const definition = findDefinition(createAssistantActionExecutor().listDefinitions(), actionIds.conversationMessagesList);
|
|
156
|
+
const result = definition.input.schema.patch({
|
|
157
|
+
targetSurfaceId: "home",
|
|
158
|
+
conversationId: "123",
|
|
159
|
+
query: {
|
|
160
|
+
page: 2,
|
|
161
|
+
pageSize: 25
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
assert.deepEqual(result.errors, {});
|
|
166
|
+
assert.deepEqual(result.validatedObject, {
|
|
167
|
+
targetSurfaceId: "home",
|
|
168
|
+
conversationId: 123,
|
|
169
|
+
query: {
|
|
170
|
+
page: 2,
|
|
171
|
+
pageSize: 25
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("assistant-runtime settings update action keeps patch nested under a schema definition", () => {
|
|
177
|
+
const definition = findDefinition(createAssistantActionExecutor().listDefinitions(), actionIds.settingsUpdate);
|
|
178
|
+
const result = definition.input.schema.patch({
|
|
179
|
+
targetSurfaceId: "home",
|
|
180
|
+
patch: {
|
|
181
|
+
systemPrompt: "Be concise."
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
assert.deepEqual(result.errors, {});
|
|
186
|
+
assert.deepEqual(result.validatedObject, {
|
|
187
|
+
targetSurfaceId: "home",
|
|
188
|
+
patch: {
|
|
189
|
+
systemPrompt: "Be concise."
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolveAssistantServerConfig
|
|
6
6
|
} from "../src/server/support/assistantServerConfig.js";
|
|
7
7
|
|
|
8
|
-
test("assistant server config resolves per-surface AI config without
|
|
8
|
+
test("assistant server config resolves per-surface AI config without app-level global fallback", () => {
|
|
9
9
|
const appConfig = {
|
|
10
10
|
assistantServer: {
|
|
11
11
|
admin: {
|
|
@@ -21,14 +21,14 @@ test("assistant server config resolves per-surface AI config without legacy glob
|
|
|
21
21
|
},
|
|
22
22
|
assistant: {
|
|
23
23
|
provider: "openai",
|
|
24
|
-
apiKey: "
|
|
24
|
+
apiKey: "global-key"
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
const env = {
|
|
28
28
|
ADMIN_ASSISTANT_AI_PROVIDER: "deepseek",
|
|
29
29
|
ADMIN_ASSISTANT_AI_API_KEY: "surface-key",
|
|
30
30
|
AI_PROVIDER: "openai",
|
|
31
|
-
AI_API_KEY: "
|
|
31
|
+
AI_API_KEY: "global-env-key"
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
assert.deepEqual(resolveAssistantServerConfig(appConfig, "admin"), {
|
|
@@ -62,7 +62,7 @@ test("assistant server config requires explicit aiConfigPrefix for each surface"
|
|
|
62
62
|
appConfig: {},
|
|
63
63
|
env: {
|
|
64
64
|
AI_PROVIDER: "anthropic",
|
|
65
|
-
AI_API_KEY: "
|
|
65
|
+
AI_API_KEY: "global-env-key"
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
"admin"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { insertTextAtSelection } from "../src/client/support/composerInputSupport.js";
|
|
4
|
+
|
|
5
|
+
test("insertTextAtSelection inserts a line break at the caret", () => {
|
|
6
|
+
const result = insertTextAtSelection("hello world", 5, 5, "\n");
|
|
7
|
+
|
|
8
|
+
assert.deepEqual(result, {
|
|
9
|
+
value: "hello\n world",
|
|
10
|
+
selectionStart: 6,
|
|
11
|
+
selectionEnd: 6
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("insertTextAtSelection replaces the active selection with a line break", () => {
|
|
16
|
+
const result = insertTextAtSelection("hello world", 5, 11, "\n");
|
|
17
|
+
|
|
18
|
+
assert.deepEqual(result, {
|
|
19
|
+
value: "hello\n",
|
|
20
|
+
selectionStart: 6,
|
|
21
|
+
selectionEnd: 6
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
+
import { createSchema } from "json-rest-schema";
|
|
3
4
|
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
5
|
+
import { createRouter } from "../../kernel/server/http/lib/router.js";
|
|
4
6
|
import { registerRoutes } from "../src/server/registerRoutes.js";
|
|
5
7
|
import { createChatService } from "../src/server/services/chatService.js";
|
|
6
8
|
|
|
@@ -26,12 +28,17 @@ function createAssistantAppConfig() {
|
|
|
26
28
|
function createWorkspaceServerScopeSupport() {
|
|
27
29
|
return Object.freeze({
|
|
28
30
|
available: true,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
params: Object.freeze({
|
|
32
|
+
schema: createSchema({
|
|
33
|
+
workspaceSlug: {
|
|
34
|
+
type: "string",
|
|
35
|
+
required: true,
|
|
36
|
+
lowercase: true,
|
|
37
|
+
minLength: 1,
|
|
38
|
+
maxLength: 160
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
mode: "patch"
|
|
35
42
|
}),
|
|
36
43
|
buildInputFromRouteParams(params = {}) {
|
|
37
44
|
const workspaceSlug = String(params?.workspaceSlug || "").trim().toLowerCase();
|
|
@@ -43,39 +50,52 @@ function createWorkspaceServerScopeSupport() {
|
|
|
43
50
|
});
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
routes.push({ method, path, options, handler });
|
|
53
|
-
}
|
|
54
|
-
};
|
|
53
|
+
function createAssistantTestApp({
|
|
54
|
+
resolveCurrentAppConfig = () => null,
|
|
55
|
+
workspaceScopeSupport = null,
|
|
56
|
+
router = null
|
|
57
|
+
} = {}) {
|
|
58
|
+
const resolvedRouter = router || createRouter();
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
return {
|
|
61
|
+
router: resolvedRouter,
|
|
62
|
+
app: {
|
|
63
|
+
make(token) {
|
|
64
|
+
if (token === "jskit.http.router") {
|
|
65
|
+
return resolvedRouter;
|
|
66
|
+
}
|
|
67
|
+
if (token === "appConfig") {
|
|
68
|
+
return resolveCurrentAppConfig();
|
|
69
|
+
}
|
|
70
|
+
if (token === "workspaces.server.scope-support" && workspaceScopeSupport) {
|
|
71
|
+
return workspaceScopeSupport;
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`Unexpected token: ${token}`);
|
|
74
|
+
},
|
|
75
|
+
has(token) {
|
|
76
|
+
if (token === "jskit.http.router") {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (token === "appConfig") {
|
|
80
|
+
return Boolean(resolveCurrentAppConfig());
|
|
81
|
+
}
|
|
82
|
+
return token === "workspaces.server.scope-support" && Boolean(workspaceScopeSupport);
|
|
66
83
|
}
|
|
67
|
-
throw new Error(`Unexpected token: ${token}`);
|
|
68
|
-
},
|
|
69
|
-
has(token) {
|
|
70
|
-
return (
|
|
71
|
-
(token === "appConfig" ? Boolean(currentAppConfig) : token === "jskit.http.router") ||
|
|
72
|
-
token === "workspaces.server.scope-support"
|
|
73
|
-
);
|
|
74
84
|
}
|
|
75
85
|
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
test("registerRoutes resolves appConfig lazily when handlers run", async () => {
|
|
89
|
+
let currentAppConfig = null;
|
|
90
|
+
const workspaceScopeSupport = createWorkspaceServerScopeSupport();
|
|
91
|
+
const testApp = createAssistantTestApp({
|
|
92
|
+
workspaceScopeSupport,
|
|
93
|
+
resolveCurrentAppConfig: () => currentAppConfig
|
|
94
|
+
});
|
|
76
95
|
|
|
77
|
-
registerRoutes(app);
|
|
96
|
+
registerRoutes(testApp.app);
|
|
78
97
|
currentAppConfig = createAssistantAppConfig();
|
|
98
|
+
const routes = testApp.router.list();
|
|
79
99
|
|
|
80
100
|
const route = routes.find(
|
|
81
101
|
(entry) =>
|
|
@@ -135,38 +155,17 @@ test("registerRoutes resolves appConfig lazily when handlers run", async () => {
|
|
|
135
155
|
});
|
|
136
156
|
|
|
137
157
|
test("registerRoutes returns clear AppError payload for pre-stream assistant failures", async () => {
|
|
138
|
-
const routes = [];
|
|
139
158
|
let currentAppConfig = null;
|
|
159
|
+
let capturedInput = null;
|
|
160
|
+
const workspaceScopeSupport = createWorkspaceServerScopeSupport();
|
|
161
|
+
const testApp = createAssistantTestApp({
|
|
162
|
+
workspaceScopeSupport,
|
|
163
|
+
resolveCurrentAppConfig: () => currentAppConfig
|
|
164
|
+
});
|
|
140
165
|
|
|
141
|
-
|
|
142
|
-
register(method, path, options, handler) {
|
|
143
|
-
routes.push({ method, path, options, handler });
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const app = {
|
|
148
|
-
make(token) {
|
|
149
|
-
if (token === "jskit.http.router") {
|
|
150
|
-
return router;
|
|
151
|
-
}
|
|
152
|
-
if (token === "appConfig") {
|
|
153
|
-
return currentAppConfig;
|
|
154
|
-
}
|
|
155
|
-
if (token === "workspaces.server.scope-support") {
|
|
156
|
-
return createWorkspaceServerScopeSupport();
|
|
157
|
-
}
|
|
158
|
-
throw new Error(`Unexpected token: ${token}`);
|
|
159
|
-
},
|
|
160
|
-
has(token) {
|
|
161
|
-
return (
|
|
162
|
-
(token === "appConfig" ? Boolean(currentAppConfig) : token === "jskit.http.router") ||
|
|
163
|
-
token === "workspaces.server.scope-support"
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
registerRoutes(app);
|
|
166
|
+
registerRoutes(testApp.app);
|
|
169
167
|
currentAppConfig = createAssistantAppConfig();
|
|
168
|
+
const routes = testApp.router.list();
|
|
170
169
|
|
|
171
170
|
const route = routes.find(
|
|
172
171
|
(entry) =>
|
|
@@ -212,7 +211,8 @@ test("registerRoutes returns clear AppError payload for pre-stream assistant fai
|
|
|
212
211
|
history: []
|
|
213
212
|
}
|
|
214
213
|
},
|
|
215
|
-
executeAction: async () => {
|
|
214
|
+
executeAction: async ({ input }) => {
|
|
215
|
+
capturedInput = input;
|
|
216
216
|
throw new AppError(503, "Assistant provider is not configured.");
|
|
217
217
|
}
|
|
218
218
|
},
|
|
@@ -224,6 +224,13 @@ test("registerRoutes returns clear AppError payload for pre-stream assistant fai
|
|
|
224
224
|
error: "Assistant provider is not configured.",
|
|
225
225
|
code: "APP_ERROR"
|
|
226
226
|
});
|
|
227
|
+
assert.deepEqual(capturedInput, {
|
|
228
|
+
targetSurfaceId: "admin",
|
|
229
|
+
workspaceSlug: "dogandgroom",
|
|
230
|
+
messageId: "msg_1",
|
|
231
|
+
input: "hello",
|
|
232
|
+
history: []
|
|
233
|
+
});
|
|
227
234
|
});
|
|
228
235
|
|
|
229
236
|
test("chat service resolves appConfig lazily when conversations are listed", async () => {
|
|
@@ -321,27 +328,12 @@ test("chat service rejects workspace-scoped assistant surfaces when workspace su
|
|
|
321
328
|
});
|
|
322
329
|
|
|
323
330
|
test("registerRoutes omits workspace assistant routes when workspace scope support is unavailable", () => {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (token === "jskit.http.router") {
|
|
328
|
-
return {
|
|
329
|
-
register(method, path, options, handler) {
|
|
330
|
-
routes.push({ method, path, options, handler });
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
if (token === "appConfig") {
|
|
335
|
-
return createAssistantAppConfig();
|
|
336
|
-
}
|
|
337
|
-
throw new Error(`Unexpected token: ${token}`);
|
|
338
|
-
},
|
|
339
|
-
has(token) {
|
|
340
|
-
return token === "jskit.http.router" || token === "appConfig";
|
|
341
|
-
}
|
|
342
|
-
};
|
|
331
|
+
const testApp = createAssistantTestApp({
|
|
332
|
+
resolveCurrentAppConfig: () => createAssistantAppConfig()
|
|
333
|
+
});
|
|
343
334
|
|
|
344
|
-
registerRoutes(app);
|
|
335
|
+
registerRoutes(testApp.app);
|
|
336
|
+
const routes = testApp.router.list();
|
|
345
337
|
|
|
346
338
|
assert.equal(
|
|
347
339
|
routes.some((entry) => entry.path.startsWith("/api/w/:workspaceSlug/assistant/")),
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
3
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
-
|
|
5
|
-
const assistantSurfaceRouteParamsValidator = Object.freeze({
|
|
6
|
-
schema: Type.Object(
|
|
7
|
-
{
|
|
8
|
-
surfaceId: Type.String({ minLength: 1, maxLength: 64 })
|
|
9
|
-
},
|
|
10
|
-
{ additionalProperties: false }
|
|
11
|
-
),
|
|
12
|
-
normalize(value = {}) {
|
|
13
|
-
return {
|
|
14
|
-
surfaceId: normalizeSurfaceId(value?.surfaceId)
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const assistantTargetSurfaceInputValidator = Object.freeze({
|
|
20
|
-
schema: Type.Object(
|
|
21
|
-
{
|
|
22
|
-
targetSurfaceId: Type.String({ minLength: 1, maxLength: 64 }),
|
|
23
|
-
workspaceSlug: Type.Optional(Type.String({ minLength: 1, maxLength: 160 }))
|
|
24
|
-
},
|
|
25
|
-
{ additionalProperties: false }
|
|
26
|
-
),
|
|
27
|
-
normalize(value = {}) {
|
|
28
|
-
const targetSurfaceId = normalizeSurfaceId(value?.targetSurfaceId);
|
|
29
|
-
const workspaceSlug = normalizeText(value?.workspaceSlug).toLowerCase();
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
targetSurfaceId,
|
|
33
|
-
...(workspaceSlug ? { workspaceSlug } : {})
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
export {
|
|
39
|
-
assistantSurfaceRouteParamsValidator,
|
|
40
|
-
assistantTargetSurfaceInputValidator
|
|
41
|
-
};
|