@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.
Files changed (30) hide show
  1. package/package.descriptor.mjs +136 -0
  2. package/package.json +21 -0
  3. package/src/client/components/AssistantSettingsClientElement.vue +204 -0
  4. package/src/client/components/AssistantSurfaceClientElement.vue +19 -0
  5. package/src/client/composables/useAssistantRuntime.js +759 -0
  6. package/src/client/index.js +4 -0
  7. package/src/client/providers/AssistantClientProvider.js +16 -0
  8. package/src/server/AssistantProvider.js +152 -0
  9. package/src/server/actionIds.js +9 -0
  10. package/src/server/actions.js +151 -0
  11. package/src/server/inputValidators.js +41 -0
  12. package/src/server/registerRoutes.js +450 -0
  13. package/src/server/repositories/assistantConfigRepository.js +148 -0
  14. package/src/server/repositories/conversationsRepository.js +263 -0
  15. package/src/server/repositories/messagesRepository.js +166 -0
  16. package/src/server/services/assistantConfigService.js +132 -0
  17. package/src/server/services/chatService.js +1048 -0
  18. package/src/server/services/transcriptService.js +331 -0
  19. package/src/server/support/assistantServerConfig.js +106 -0
  20. package/src/server/support/createSurfaceAwareToolCatalog.js +64 -0
  21. package/src/shared/assistantRuntimeConfig.js +7 -0
  22. package/src/shared/assistantSurfaces.js +97 -0
  23. package/src/shared/index.js +7 -0
  24. package/templates/migrations/assistant_config_initial.cjs +27 -0
  25. package/templates/migrations/assistant_transcripts_initial.cjs +58 -0
  26. package/test/assistantServerConfig.test.js +72 -0
  27. package/test/assistantSurfaces.test.js +50 -0
  28. package/test/createSurfaceAwareToolCatalog.test.js +77 -0
  29. package/test/lazyAppConfig.test.js +248 -0
  30. package/test/packageDescriptor.test.js +34 -0
@@ -0,0 +1,136 @@
1
+ export default Object.freeze({
2
+ packageVersion: 1,
3
+ packageId: "@jskit-ai/assistant-runtime",
4
+ version: "0.1.1",
5
+ kind: "runtime",
6
+ description: "Shared assistant runtime with per-surface assistant registration.",
7
+ dependsOn: [
8
+ "@jskit-ai/assistant-core",
9
+ "@jskit-ai/database-runtime",
10
+ "@jskit-ai/http-runtime",
11
+ "@jskit-ai/shell-web",
12
+ "@jskit-ai/users-core",
13
+ "@jskit-ai/users-web"
14
+ ],
15
+ capabilities: {
16
+ provides: ["assistant.runtime"],
17
+ requires: [
18
+ "runtime.actions",
19
+ "runtime.database",
20
+ "auth.policy",
21
+ "runtime.http-client",
22
+ "users.core",
23
+ "users.web"
24
+ ]
25
+ },
26
+ runtime: {
27
+ server: {
28
+ providers: [
29
+ {
30
+ entrypoint: "src/server/AssistantProvider.js",
31
+ export: "AssistantProvider"
32
+ }
33
+ ]
34
+ },
35
+ client: {
36
+ providers: [
37
+ {
38
+ entrypoint: "src/client/providers/AssistantClientProvider.js",
39
+ export: "AssistantClientProvider"
40
+ }
41
+ ]
42
+ }
43
+ },
44
+ metadata: {
45
+ apiSummary: {
46
+ surfaces: [
47
+ {
48
+ subpath: "./client",
49
+ summary: "Exports assistant runtime client elements and composables."
50
+ },
51
+ {
52
+ subpath: "./shared",
53
+ summary: "Exports assistant runtime shared config helpers."
54
+ },
55
+ {
56
+ subpath: "./server/actionIds",
57
+ summary: "Exports assistant runtime action identifiers."
58
+ }
59
+ ],
60
+ containerTokens: {
61
+ server: [
62
+ "assistant.config.repository",
63
+ "assistant.conversation.repository",
64
+ "assistant.message.repository",
65
+ "assistant.ai.client.factory",
66
+ "assistant.service.tool-catalog"
67
+ ],
68
+ client: [
69
+ "assistant.web.settings.element"
70
+ ]
71
+ }
72
+ }
73
+ },
74
+ mutations: {
75
+ dependencies: {
76
+ runtime: {
77
+ "@jskit-ai/assistant-core": "0.1.6",
78
+ "@jskit-ai/database-runtime": "0.1.30",
79
+ "@jskit-ai/http-runtime": "0.1.29",
80
+ "@jskit-ai/kernel": "0.1.30",
81
+ "@jskit-ai/shell-web": "0.1.29",
82
+ "@jskit-ai/users-core": "0.1.40",
83
+ "@jskit-ai/users-web": "0.1.45",
84
+ "@tanstack/vue-query": "^5.90.5",
85
+ "vuetify": "^4.0.0"
86
+ },
87
+ dev: {}
88
+ },
89
+ packageJson: {
90
+ scripts: {}
91
+ },
92
+ procfile: {},
93
+ files: [
94
+ {
95
+ op: "install-migration",
96
+ from: "templates/migrations/assistant_config_initial.cjs",
97
+ toDir: "migrations",
98
+ extension: ".cjs",
99
+ reason: "Install assistant configuration schema migration.",
100
+ category: "assistant-runtime",
101
+ id: "assistant-runtime-config-initial-schema"
102
+ },
103
+ {
104
+ op: "install-migration",
105
+ from: "templates/migrations/assistant_transcripts_initial.cjs",
106
+ toDir: "migrations",
107
+ extension: ".cjs",
108
+ reason: "Install assistant transcript schema migration.",
109
+ category: "assistant-runtime",
110
+ id: "assistant-runtime-transcripts-initial-schema"
111
+ }
112
+ ],
113
+ text: [
114
+ {
115
+ op: "append-text",
116
+ file: "config/public.js",
117
+ position: "bottom",
118
+ skipIfContains: "config.assistantSurfaces ||= {};",
119
+ value: "\nconfig.assistantSurfaces ||= {};\n",
120
+ reason: "Initialize the shared assistant surface registry in public app config.",
121
+ category: "assistant-runtime",
122
+ id: "assistant-runtime-public-surface-registry-init"
123
+ },
124
+ {
125
+ op: "append-text",
126
+ file: "config/server.js",
127
+ position: "bottom",
128
+ skipIfContains: "config.assistantServer ||= {};",
129
+ value: "\nconfig.assistantServer ||= {};\n",
130
+ reason: "Initialize the shared assistant server config registry.",
131
+ category: "assistant-runtime",
132
+ id: "assistant-runtime-server-surface-registry-init"
133
+ }
134
+ ]
135
+ }
136
+ });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@jskit-ai/assistant-runtime",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "exports": {
6
+ "./client": "./src/client/index.js",
7
+ "./shared": "./src/shared/index.js",
8
+ "./server/actionIds": "./src/server/actionIds.js"
9
+ },
10
+ "dependencies": {
11
+ "@jskit-ai/assistant-core": "0.1.6",
12
+ "@jskit-ai/database-runtime": "0.1.30",
13
+ "@jskit-ai/http-runtime": "0.1.29",
14
+ "@jskit-ai/kernel": "0.1.30",
15
+ "@jskit-ai/shell-web": "0.1.29",
16
+ "@jskit-ai/users-core": "0.1.40",
17
+ "@jskit-ai/users-web": "0.1.45",
18
+ "@tanstack/vue-query": "^5.90.5",
19
+ "vuetify": "^4.0.0"
20
+ }
21
+ }
@@ -0,0 +1,204 @@
1
+ <template>
2
+ <AssistantSettingsFormCard
3
+ root-class="assistant-settings-client-element"
4
+ title="Assistant settings"
5
+ :subtitle="subtitle"
6
+ no-permission-message="You do not have permission to view assistant settings."
7
+ save-label="Save assistant settings"
8
+ :add-edit="addEdit"
9
+ :show-form-skeleton="showFormSkeleton"
10
+ >
11
+ <v-textarea
12
+ v-model="form.systemPrompt"
13
+ label="System prompt"
14
+ variant="outlined"
15
+ density="comfortable"
16
+ rows="8"
17
+ auto-grow
18
+ :readonly="!addEdit.canSave || addEdit.isSaving || addEdit.isRefetching"
19
+ :error-messages="fieldErrors.systemPrompt ? [fieldErrors.systemPrompt] : []"
20
+ />
21
+ </AssistantSettingsFormCard>
22
+ </template>
23
+
24
+ <script setup>
25
+ import { computed, reactive, watch } from "vue";
26
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
27
+ import { getClientAppConfig } from "@jskit-ai/kernel/client";
28
+ import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
29
+ import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
30
+ import { assistantHttpClient, createAssistantApi, AssistantSettingsFormCard } from "@jskit-ai/assistant-core/client";
31
+ import { assistantConfigResource, assistantSettingsQueryKey, buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
32
+ import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
33
+ import { useWorkspaceRouteContext } from "@jskit-ai/users-web/client/composables/useWorkspaceRouteContext";
34
+ import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
35
+
36
+ const props = defineProps({
37
+ targetSurfaceId: {
38
+ type: String,
39
+ required: true
40
+ }
41
+ });
42
+
43
+ const form = reactive({
44
+ systemPrompt: ""
45
+ });
46
+
47
+ const fieldErrors = reactive({
48
+ systemPrompt: ""
49
+ });
50
+
51
+ const errorRuntime = useShellWebErrorRuntime();
52
+ const queryClient = useQueryClient();
53
+ const appConfig = getClientAppConfig();
54
+ const { placementContext, currentSurfaceId, workspaceSlugFromRoute } = useWorkspaceRouteContext();
55
+
56
+ const assistantSurface = computed(() => resolveAssistantSurfaceConfig(appConfig, props.targetSurfaceId));
57
+ const placementSnapshot = computed(() => normalizeObject(placementContext.value));
58
+ const scope = computed(() => {
59
+ const settingsRequiresWorkspace = assistantSurface.value?.settingsSurfaceRequiresWorkspace === true;
60
+ const workspaceSlug = settingsRequiresWorkspace
61
+ ? normalizeText(workspaceSlugFromRoute.value).toLowerCase()
62
+ : "";
63
+
64
+ return {
65
+ targetSurfaceId: normalizeText(assistantSurface.value?.targetSurfaceId).toLowerCase(),
66
+ workspaceSlug,
67
+ workspaceId: settingsRequiresWorkspace
68
+ ? Number(placementSnapshot.value?.workspace?.id || 0) || 0
69
+ : 0
70
+ };
71
+ });
72
+ const hasScope = computed(() =>
73
+ Boolean(assistantSurface.value) &&
74
+ (assistantSurface.value?.settingsSurfaceRequiresWorkspace ? Boolean(scope.value.workspaceSlug) : true)
75
+ );
76
+ const queryKey = computed(() => assistantSettingsQueryKey(scope.value));
77
+
78
+ const settingsApi = createAssistantApi({
79
+ request: assistantHttpClient.request,
80
+ requestStream: assistantHttpClient.requestStream,
81
+ resolveBasePath: () =>
82
+ buildAssistantApiPath({
83
+ requiresWorkspace: assistantSurface.value?.settingsSurfaceRequiresWorkspace === true,
84
+ workspaceSlug: scope.value.workspaceSlug,
85
+ suffix: `/${scope.value.targetSurfaceId}`
86
+ }),
87
+ resolveSurfaceId: () => normalizeText(currentSurfaceId.value).toLowerCase()
88
+ });
89
+
90
+ const subtitle = computed(() => {
91
+ if (!assistantSurface.value) {
92
+ return "Configure assistant settings.";
93
+ }
94
+
95
+ if (assistantSurface.value.configScope === "workspace") {
96
+ return `Configure the prompt used on the ${assistantSurface.value.targetSurfaceId} surface for this workspace.`;
97
+ }
98
+
99
+ return `Configure the prompt used on the ${assistantSurface.value.targetSurfaceId} surface.`;
100
+ });
101
+
102
+ function clearFieldErrors() {
103
+ fieldErrors.systemPrompt = "";
104
+ }
105
+
106
+ function reportAssistantSettingsError(message, dedupeKey) {
107
+ const normalizedMessage = normalizeText(message);
108
+ if (!normalizedMessage) {
109
+ return;
110
+ }
111
+
112
+ errorRuntime.report({
113
+ source: "assistant.settings",
114
+ message: normalizedMessage,
115
+ severity: "error",
116
+ channel: "banner",
117
+ dedupeKey,
118
+ dedupeWindowMs: 3000
119
+ });
120
+ }
121
+
122
+ const settingsQuery = useQuery({
123
+ queryKey,
124
+ enabled: hasScope,
125
+ retry: false,
126
+ queryFn: () => settingsApi.getSettings()
127
+ });
128
+
129
+ watch(
130
+ () => settingsQuery.data.value,
131
+ (payload) => {
132
+ const settings = payload?.settings && typeof payload.settings === "object" ? payload.settings : {};
133
+ form.systemPrompt = String(settings.systemPrompt || "");
134
+ clearFieldErrors();
135
+ },
136
+ {
137
+ immediate: true
138
+ }
139
+ );
140
+
141
+ const settingsMutation = useMutation({
142
+ mutationFn: (patch) => settingsApi.updateSettings(patch),
143
+ onSuccess(payload) {
144
+ queryClient.setQueryData(queryKey.value, payload);
145
+ }
146
+ });
147
+
148
+ const loadError = computed(() => {
149
+ if (!assistantSurface.value) {
150
+ return "Assistant settings are not configured for this surface.";
151
+ }
152
+ if (hasScope.value) {
153
+ return normalizeText(settingsQuery.error.value?.message);
154
+ }
155
+
156
+ if (assistantSurface.value?.settingsSurfaceRequiresWorkspace) {
157
+ return "Select a workspace to configure assistant settings.";
158
+ }
159
+
160
+ return "";
161
+ });
162
+
163
+ async function submit() {
164
+ clearFieldErrors();
165
+
166
+ const validation = validateOperationSection({
167
+ operation: assistantConfigResource.operations.patch,
168
+ section: "bodyValidator",
169
+ value: {
170
+ systemPrompt: form.systemPrompt
171
+ }
172
+ });
173
+ if (!validation.ok) {
174
+ fieldErrors.systemPrompt = String(validation.fieldErrors.systemPrompt || "");
175
+ if (validation.globalErrors.length > 0) {
176
+ reportAssistantSettingsError(validation.globalErrors[0], "assistant.settings:validation");
177
+ }
178
+ return;
179
+ }
180
+
181
+ try {
182
+ await settingsMutation.mutateAsync(validation.value);
183
+ } catch (error) {
184
+ const nextFieldErrors = normalizeObject(error?.details?.fieldErrors);
185
+ fieldErrors.systemPrompt = String(nextFieldErrors.systemPrompt || "");
186
+ reportAssistantSettingsError(
187
+ normalizeText(error?.message) || "Unable to update assistant settings.",
188
+ "assistant.settings:save"
189
+ );
190
+ }
191
+ }
192
+
193
+ const addEdit = computed(() => ({
194
+ canView: Boolean(assistantSurface.value) && hasScope.value,
195
+ canSave: Boolean(assistantSurface.value) && hasScope.value,
196
+ loadError: loadError.value,
197
+ isInitialLoading: Boolean(settingsQuery.isLoading.value),
198
+ isRefetching: Boolean(settingsQuery.isFetching.value && !settingsQuery.isLoading.value),
199
+ isSaving: Boolean(settingsMutation.isPending.value),
200
+ submit
201
+ }));
202
+
203
+ const showFormSkeleton = computed(() => Boolean(settingsQuery.isLoading.value));
204
+ </script>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <AssistantClientElement v-bind="runtime" />
3
+ </template>
4
+
5
+ <script setup>
6
+ import { AssistantClientElement } from "@jskit-ai/assistant-core/client";
7
+ import { useAssistantRuntime } from "../composables/useAssistantRuntime.js";
8
+
9
+ const props = defineProps({
10
+ surfaceId: {
11
+ type: String,
12
+ required: true
13
+ }
14
+ });
15
+
16
+ const runtime = useAssistantRuntime({
17
+ surfaceId: props.surfaceId
18
+ });
19
+ </script>