@jskit-ai/assistant-runtime 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/assistant-runtime",
4
- version: "0.1.15",
4
+ version: "0.1.17",
5
5
  kind: "runtime",
6
6
  description: "Shared assistant runtime with per-surface assistant registration.",
7
7
  dependsOn: [
@@ -10,9 +10,7 @@ export default Object.freeze({
10
10
  "@jskit-ai/http-runtime",
11
11
  "@jskit-ai/shell-web",
12
12
  "@jskit-ai/users-core",
13
- "@jskit-ai/users-web",
14
- "@jskit-ai/workspaces-core",
15
- "@jskit-ai/workspaces-web"
13
+ "@jskit-ai/users-web"
16
14
  ],
17
15
  capabilities: {
18
16
  provides: ["assistant.runtime"],
@@ -22,9 +20,7 @@ export default Object.freeze({
22
20
  "auth.policy",
23
21
  "runtime.http-client",
24
22
  "users.core",
25
- "users.web",
26
- "workspaces.core",
27
- "workspaces.web"
23
+ "users.web"
28
24
  ]
29
25
  },
30
26
  runtime: {
@@ -78,15 +74,13 @@ export default Object.freeze({
78
74
  mutations: {
79
75
  dependencies: {
80
76
  runtime: {
81
- "@jskit-ai/assistant-core": "0.1.20",
82
- "@jskit-ai/database-runtime": "0.1.44",
83
- "@jskit-ai/http-runtime": "0.1.43",
84
- "@jskit-ai/kernel": "0.1.44",
85
- "@jskit-ai/shell-web": "0.1.43",
86
- "@jskit-ai/users-core": "0.1.54",
87
- "@jskit-ai/users-web": "0.1.59",
88
- "@jskit-ai/workspaces-core": "0.1.20",
89
- "@jskit-ai/workspaces-web": "0.1.20",
77
+ "@jskit-ai/assistant-core": "0.1.22",
78
+ "@jskit-ai/database-runtime": "0.1.46",
79
+ "@jskit-ai/http-runtime": "0.1.45",
80
+ "@jskit-ai/kernel": "0.1.46",
81
+ "@jskit-ai/shell-web": "0.1.45",
82
+ "@jskit-ai/users-core": "0.1.56",
83
+ "@jskit-ai/users-web": "0.1.61",
90
84
  "@tanstack/vue-query": "^5.90.5",
91
85
  "vuetify": "^4.0.0"
92
86
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/assistant-runtime",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./client": "./src/client/index.js",
@@ -8,15 +8,13 @@
8
8
  "./server/actionIds": "./src/server/actionIds.js"
9
9
  },
10
10
  "dependencies": {
11
- "@jskit-ai/assistant-core": "0.1.20",
12
- "@jskit-ai/database-runtime": "0.1.44",
13
- "@jskit-ai/http-runtime": "0.1.43",
14
- "@jskit-ai/kernel": "0.1.44",
15
- "@jskit-ai/shell-web": "0.1.43",
16
- "@jskit-ai/users-core": "0.1.54",
17
- "@jskit-ai/users-web": "0.1.59",
18
- "@jskit-ai/workspaces-core": "0.1.20",
19
- "@jskit-ai/workspaces-web": "0.1.20",
11
+ "@jskit-ai/assistant-core": "0.1.22",
12
+ "@jskit-ai/database-runtime": "0.1.46",
13
+ "@jskit-ai/http-runtime": "0.1.45",
14
+ "@jskit-ai/kernel": "0.1.46",
15
+ "@jskit-ai/shell-web": "0.1.45",
16
+ "@jskit-ai/users-core": "0.1.56",
17
+ "@jskit-ai/users-web": "0.1.61",
20
18
  "@tanstack/vue-query": "^5.90.5",
21
19
  "vuetify": "^4.0.0"
22
20
  }
@@ -30,8 +30,9 @@ import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validato
30
30
  import { assistantHttpClient, createAssistantApi, AssistantSettingsFormCard } from "@jskit-ai/assistant-core/client";
31
31
  import { assistantConfigResource, assistantSettingsQueryKey, buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
32
32
  import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
33
- import { useWorkspaceRouteContext } from "@jskit-ai/workspaces-web/client/composables/useWorkspaceRouteContext";
33
+ import { useSurfaceRouteContext } from "@jskit-ai/users-web/client/composables/useSurfaceRouteContext";
34
34
  import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
35
+ import { useWorkspaceWebScopeSupport } from "../support/workspaceScopeSupport.js";
35
36
 
36
37
  const props = defineProps({
37
38
  targetSurfaceId: {
@@ -51,14 +52,17 @@ const fieldErrors = reactive({
51
52
  const errorRuntime = useShellWebErrorRuntime();
52
53
  const queryClient = useQueryClient();
53
54
  const appConfig = getClientAppConfig();
54
- const { placementContext, currentSurfaceId, workspaceSlugFromRoute } = useWorkspaceRouteContext();
55
+ const routeContext = useSurfaceRouteContext();
56
+ const workspaceScopeSupport = useWorkspaceWebScopeSupport();
57
+ const { placementContext, currentSurfaceId } = routeContext;
55
58
 
56
59
  const assistantSurface = computed(() => resolveAssistantSurfaceConfig(appConfig, props.targetSurfaceId));
57
60
  const placementSnapshot = computed(() => normalizeObject(placementContext.value));
61
+ const routeScope = computed(() => workspaceScopeSupport.readRouteScope(routeContext));
58
62
  const scope = computed(() => {
59
63
  const settingsRequiresWorkspace = assistantSurface.value?.settingsSurfaceRequiresWorkspace === true;
60
64
  const workspaceSlug = settingsRequiresWorkspace
61
- ? normalizeText(workspaceSlugFromRoute.value).toLowerCase()
65
+ ? normalizeText(routeScope.value.workspaceSlug).toLowerCase()
62
66
  : "";
63
67
 
64
68
  return {
@@ -154,6 +158,10 @@ const loadError = computed(() => {
154
158
  }
155
159
 
156
160
  if (assistantSurface.value?.settingsSurfaceRequiresWorkspace) {
161
+ if (workspaceScopeSupport.available !== true) {
162
+ return "Workspace support is not available for this assistant surface.";
163
+ }
164
+
157
165
  return "Select a workspace to configure assistant settings.";
158
166
  }
159
167
 
@@ -21,8 +21,9 @@ import {
21
21
  } from "@jskit-ai/assistant-core/client";
22
22
  import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
23
23
  import { usePagedCollection } from "@jskit-ai/users-web/client/composables/usePagedCollection";
24
- import { useWorkspaceRouteContext } from "@jskit-ai/workspaces-web/client/composables/useWorkspaceRouteContext";
24
+ import { useSurfaceRouteContext } from "@jskit-ai/users-web/client/composables/useSurfaceRouteContext";
25
25
  import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
26
+ import { useWorkspaceWebScopeSupport } from "../support/workspaceScopeSupport.js";
26
27
 
27
28
  const DEFAULT_STREAM_TIMEOUT_MS = 120_000;
28
29
  const DEFAULT_HISTORY_PAGE_SIZE = 20;
@@ -234,7 +235,9 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
234
235
  const runtimePolicy = resolveRuntimePolicy();
235
236
  const queryClient = useQueryClient();
236
237
  const errorRuntime = useShellWebErrorRuntime();
237
- const { placementContext, currentSurfaceId, workspaceSlugFromRoute } = useWorkspaceRouteContext();
238
+ const routeContext = useSurfaceRouteContext();
239
+ const workspaceScopeSupport = useWorkspaceWebScopeSupport();
240
+ const { placementContext, currentSurfaceId } = routeContext;
238
241
  const appConfig = getClientAppConfig();
239
242
 
240
243
  const messages = ref([]);
@@ -250,9 +253,10 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
250
253
  const assistantSurface = computed(() =>
251
254
  resolveAssistantSurfaceConfig(appConfig, surfaceId)
252
255
  );
256
+ const routeScope = computed(() => workspaceScopeSupport.readRouteScope(routeContext));
253
257
  const runtimeScope = computed(() => {
254
258
  const workspaceSlug = assistantSurface.value?.runtimeSurfaceRequiresWorkspace
255
- ? normalizeText(workspaceSlugFromRoute.value).toLowerCase()
259
+ ? normalizeText(routeScope.value.workspaceSlug).toLowerCase()
256
260
  : "";
257
261
 
258
262
  return {
@@ -0,0 +1,38 @@
1
+ import { inject } from "vue";
2
+
3
+ const WORKSPACES_WEB_SCOPE_SUPPORT_INJECTION_KEY = "jskit.workspaces.web.scope-support";
4
+
5
+ const EMPTY_ROUTE_SCOPE = Object.freeze({
6
+ workspaceSlug: ""
7
+ });
8
+
9
+ const EMPTY_WORKSPACE_WEB_SCOPE_SUPPORT = Object.freeze({
10
+ available: false,
11
+ readRouteScope() {
12
+ return EMPTY_ROUTE_SCOPE;
13
+ }
14
+ });
15
+
16
+ function isWorkspaceWebScopeSupport(value) {
17
+ return Boolean(value && typeof value.readRouteScope === "function");
18
+ }
19
+
20
+ function useWorkspaceWebScopeSupport({ required = false } = {}) {
21
+ const support = inject(WORKSPACES_WEB_SCOPE_SUPPORT_INJECTION_KEY, null);
22
+ if (isWorkspaceWebScopeSupport(support)) {
23
+ return support;
24
+ }
25
+
26
+ if (required) {
27
+ throw new Error("Workspace web scope support is not available in Vue injection context.");
28
+ }
29
+
30
+ return EMPTY_WORKSPACE_WEB_SCOPE_SUPPORT;
31
+ }
32
+
33
+ export {
34
+ EMPTY_WORKSPACE_WEB_SCOPE_SUPPORT,
35
+ WORKSPACES_WEB_SCOPE_SUPPORT_INJECTION_KEY,
36
+ isWorkspaceWebScopeSupport,
37
+ useWorkspaceWebScopeSupport
38
+ };
@@ -12,6 +12,7 @@ import { createChatService } from "./services/chatService.js";
12
12
  import { createTranscriptService } from "./services/transcriptService.js";
13
13
  import { resolveAssistantAiConfig } from "./support/assistantServerConfig.js";
14
14
  import { createSurfaceAwareToolCatalog } from "./support/createSurfaceAwareToolCatalog.js";
15
+ import { resolveWorkspaceServerScopeSupport } from "./support/workspaceScopeSupport.js";
15
16
 
16
17
  function resolveGlobalAssistantConfig(scope) {
17
18
  const appConfig = resolveAppConfig(scope);
@@ -108,7 +109,8 @@ class AssistantProvider {
108
109
  createAssistantConfigService({
109
110
  assistantConfigRepository: scope.make("assistant.config.repository"),
110
111
  consoleService: scope.has("consoleService") ? scope.make("consoleService") : null,
111
- resolveAppConfig: resolveCurrentAppConfig
112
+ resolveAppConfig: resolveCurrentAppConfig,
113
+ workspaceScopeSupport: resolveWorkspaceServerScopeSupport(scope)
112
114
  })
113
115
  );
114
116
 
@@ -129,7 +131,8 @@ class AssistantProvider {
129
131
  transcriptService: scope.make("assistant.transcript.service"),
130
132
  serviceToolCatalog: scope.make("assistant.service.tool-catalog"),
131
133
  assistantConfigService: scope.make("assistant.config.service"),
132
- resolveAppConfig: resolveCurrentAppConfig
134
+ resolveAppConfig: resolveCurrentAppConfig,
135
+ workspaceScopeSupport: resolveWorkspaceServerScopeSupport(scope)
133
136
  })
134
137
  );
135
138
 
@@ -2,8 +2,6 @@ import { AppError } from "@jskit-ai/kernel/server/runtime";
2
2
  import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
3
3
  import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
4
4
  import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
5
- import { buildWorkspaceInputFromRouteParams } from "@jskit-ai/workspaces-core/server/support/workspaceRouteInput";
6
- import { workspaceSlugParamsValidator } from "@jskit-ai/workspaces-core/server/validators/routeParamsValidator";
7
5
  import {
8
6
  assistantConfigResource,
9
7
  assistantResource,
@@ -18,17 +16,22 @@ import {
18
16
  import { resolveAssistantSurfaceConfig } from "../shared/assistantSurfaces.js";
19
17
  import { actionIds } from "./actionIds.js";
20
18
  import { assistantSurfaceRouteParamsValidator } from "./inputValidators.js";
19
+ import { resolveWorkspaceServerScopeSupport } from "./support/workspaceScopeSupport.js";
21
20
 
22
- function buildRouteParamsValidator(requiresWorkspace) {
21
+ function buildRouteParamsValidator(requiresWorkspace, workspaceScopeSupport = null) {
23
22
  if (requiresWorkspace === true) {
24
- return [workspaceSlugParamsValidator, assistantSurfaceRouteParamsValidator];
23
+ if (!workspaceScopeSupport) {
24
+ throw new Error("Assistant workspace routes require workspace server scope support.");
25
+ }
26
+
27
+ return [workspaceScopeSupport.paramsValidator, assistantSurfaceRouteParamsValidator];
25
28
  }
26
29
 
27
30
  return assistantSurfaceRouteParamsValidator;
28
31
  }
29
32
 
30
- function buildConversationMessagesRouteParamsValidator(requiresWorkspace) {
31
- const validators = buildRouteParamsValidator(requiresWorkspace);
33
+ function buildConversationMessagesRouteParamsValidator(requiresWorkspace, workspaceScopeSupport = null) {
34
+ const validators = buildRouteParamsValidator(requiresWorkspace, workspaceScopeSupport);
32
35
  if (Array.isArray(validators)) {
33
36
  return validators.concat(assistantResource.operations.conversationMessagesList.paramsValidator);
34
37
  }
@@ -36,12 +39,16 @@ function buildConversationMessagesRouteParamsValidator(requiresWorkspace) {
36
39
  return [validators, assistantResource.operations.conversationMessagesList.paramsValidator];
37
40
  }
38
41
 
39
- function readWorkspaceInput(request, requiresWorkspace) {
42
+ function readWorkspaceInput(request, requiresWorkspace, workspaceScopeSupport = null) {
40
43
  if (requiresWorkspace !== true) {
41
44
  return {};
42
45
  }
43
46
 
44
- return buildWorkspaceInputFromRouteParams(request?.input?.params);
47
+ if (!workspaceScopeSupport) {
48
+ throw new Error("Assistant workspace routes require workspace server scope support.");
49
+ }
50
+
51
+ return workspaceScopeSupport.buildInputFromRouteParams(request?.input?.params);
45
52
  }
46
53
 
47
54
  function requireAssistantSurface(appConfig = {}, targetSurfaceId = "") {
@@ -102,7 +109,15 @@ function sendPreStreamErrorResponse(reply, error) {
102
109
  });
103
110
  }
104
111
 
105
- function resolveRouteRequestState(request, { resolveCurrentAppConfig = () => ({}), kind = "runtime", requiresWorkspace = false } = {}) {
112
+ function resolveRouteRequestState(
113
+ request,
114
+ {
115
+ resolveCurrentAppConfig = () => ({}),
116
+ kind = "runtime",
117
+ requiresWorkspace = false,
118
+ workspaceScopeSupport = null
119
+ } = {}
120
+ ) {
106
121
  const appConfig = resolveCurrentAppConfig();
107
122
  const targetSurfaceId = normalizeSurfaceId(request?.input?.params?.surfaceId);
108
123
  const assistantSurface = requireAssistantSurface(appConfig, targetSurfaceId);
@@ -126,18 +141,22 @@ function resolveRouteRequestState(request, { resolveCurrentAppConfig = () => ({}
126
141
  hostSurfaceId,
127
142
  actionInput: Object.freeze({
128
143
  targetSurfaceId: assistantSurface.targetSurfaceId,
129
- ...readWorkspaceInput(request, requiresWorkspace)
144
+ ...readWorkspaceInput(request, requiresWorkspace, workspaceScopeSupport)
130
145
  })
131
146
  });
132
147
  }
133
148
 
134
- function registerSettingsRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false } = {}) {
149
+ function registerSettingsRoutes(
150
+ router,
151
+ resolveCurrentAppConfig,
152
+ { requiresWorkspace = false, workspaceScopeSupport = null } = {}
153
+ ) {
135
154
  const routeBase = resolveAssistantApiBasePath({
136
155
  requiresWorkspace
137
156
  });
138
157
  const visibility = requiresWorkspace ? "workspace" : "public";
139
158
  const routePath = `${routeBase}/:surfaceId/settings`;
140
- const paramsValidator = buildRouteParamsValidator(requiresWorkspace);
159
+ const paramsValidator = buildRouteParamsValidator(requiresWorkspace, workspaceScopeSupport);
141
160
 
142
161
  router.register(
143
162
  "GET",
@@ -158,7 +177,8 @@ function registerSettingsRoutes(router, resolveCurrentAppConfig, { requiresWorks
158
177
  const routeState = resolveRouteRequestState(request, {
159
178
  resolveCurrentAppConfig,
160
179
  kind: "settings",
161
- requiresWorkspace
180
+ requiresWorkspace,
181
+ workspaceScopeSupport
162
182
  });
163
183
 
164
184
  const response = await request.executeAction({
@@ -198,7 +218,8 @@ function registerSettingsRoutes(router, resolveCurrentAppConfig, { requiresWorks
198
218
  const routeState = resolveRouteRequestState(request, {
199
219
  resolveCurrentAppConfig,
200
220
  kind: "settings",
201
- requiresWorkspace
221
+ requiresWorkspace,
222
+ workspaceScopeSupport
202
223
  });
203
224
 
204
225
  const response = await request.executeAction({
@@ -217,13 +238,17 @@ function registerSettingsRoutes(router, resolveCurrentAppConfig, { requiresWorks
217
238
  );
218
239
  }
219
240
 
220
- function registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false } = {}) {
241
+ function registerRuntimeRoutes(
242
+ router,
243
+ resolveCurrentAppConfig,
244
+ { requiresWorkspace = false, workspaceScopeSupport = null } = {}
245
+ ) {
221
246
  const routeBase = resolveAssistantApiBasePath({
222
247
  requiresWorkspace
223
248
  });
224
249
  const visibility = requiresWorkspace ? "workspace" : "public";
225
250
  const surfaceRouteBase = `${routeBase}/:surfaceId`;
226
- const paramsValidator = buildRouteParamsValidator(requiresWorkspace);
251
+ const paramsValidator = buildRouteParamsValidator(requiresWorkspace, workspaceScopeSupport);
227
252
 
228
253
  router.register(
229
254
  "POST",
@@ -242,7 +267,8 @@ function registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorksp
242
267
  const routeState = resolveRouteRequestState(request, {
243
268
  resolveCurrentAppConfig,
244
269
  kind: "runtime",
245
- requiresWorkspace
270
+ requiresWorkspace,
271
+ workspaceScopeSupport
246
272
  });
247
273
  const abortController = new AbortController();
248
274
  const requestBody = request?.input?.body && typeof request.input.body === "object" ? request.input.body : {};
@@ -367,7 +393,8 @@ function registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorksp
367
393
  const routeState = resolveRouteRequestState(request, {
368
394
  resolveCurrentAppConfig,
369
395
  kind: "runtime",
370
- requiresWorkspace
396
+ requiresWorkspace,
397
+ workspaceScopeSupport
371
398
  });
372
399
 
373
400
  const response = await request.executeAction({
@@ -391,7 +418,7 @@ function registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorksp
391
418
  {
392
419
  auth: "required",
393
420
  visibility,
394
- paramsValidator: buildConversationMessagesRouteParamsValidator(requiresWorkspace),
421
+ paramsValidator: buildConversationMessagesRouteParamsValidator(requiresWorkspace, workspaceScopeSupport),
395
422
  meta: {
396
423
  tags: ["assistant"],
397
424
  summary: "List assistant conversation messages."
@@ -405,7 +432,8 @@ function registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorksp
405
432
  const routeState = resolveRouteRequestState(request, {
406
433
  resolveCurrentAppConfig,
407
434
  kind: "runtime",
408
- requiresWorkspace
435
+ requiresWorkspace,
436
+ workspaceScopeSupport
409
437
  });
410
438
 
411
439
  const response = await request.executeAction({
@@ -432,19 +460,25 @@ function registerRoutes(app) {
432
460
 
433
461
  const router = app.make("jskit.http.router");
434
462
  const resolveCurrentAppConfig = () => resolveAppConfig(app);
463
+ const workspaceScopeSupport = resolveWorkspaceServerScopeSupport(app);
435
464
 
436
465
  registerSettingsRoutes(router, resolveCurrentAppConfig, {
437
466
  requiresWorkspace: false
438
467
  });
439
- registerSettingsRoutes(router, resolveCurrentAppConfig, {
440
- requiresWorkspace: true
441
- });
442
468
  registerRuntimeRoutes(router, resolveCurrentAppConfig, {
443
469
  requiresWorkspace: false
444
470
  });
445
- registerRuntimeRoutes(router, resolveCurrentAppConfig, {
446
- requiresWorkspace: true
447
- });
471
+
472
+ if (workspaceScopeSupport) {
473
+ registerSettingsRoutes(router, resolveCurrentAppConfig, {
474
+ requiresWorkspace: true,
475
+ workspaceScopeSupport
476
+ });
477
+ registerRuntimeRoutes(router, resolveCurrentAppConfig, {
478
+ requiresWorkspace: true,
479
+ workspaceScopeSupport
480
+ });
481
+ }
448
482
  }
449
483
 
450
484
  export { registerRoutes };
@@ -1,9 +1,14 @@
1
1
  import { AppError, requireAuth } from "@jskit-ai/kernel/server/runtime";
2
2
  import { normalizeObject, normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
3
- import { resolveWorkspace } from "@jskit-ai/workspaces-core/server/support/resolveWorkspace";
4
3
  import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
5
4
 
6
- function createService({ assistantConfigRepository, consoleService = null, appConfig = {}, resolveAppConfig = null } = {}) {
5
+ function createService({
6
+ assistantConfigRepository,
7
+ consoleService = null,
8
+ appConfig = {},
9
+ resolveAppConfig = null,
10
+ workspaceScopeSupport = null
11
+ } = {}) {
7
12
  if (!assistantConfigRepository || typeof assistantConfigRepository.findByScope !== "function") {
8
13
  throw new Error("assistantConfigService requires assistantConfigRepository.findByScope().");
9
14
  }
@@ -63,7 +68,11 @@ function createService({ assistantConfigRepository, consoleService = null, appCo
63
68
  return null;
64
69
  }
65
70
 
66
- const resolvedWorkspace = workspace || resolveWorkspace(context, input);
71
+ if (!workspaceScopeSupport || typeof workspaceScopeSupport.resolveWorkspace !== "function") {
72
+ throw new Error("assistant.config.service requires workspace server scope support for workspace-scoped settings.");
73
+ }
74
+
75
+ const resolvedWorkspace = workspace || workspaceScopeSupport.resolveWorkspace(context, input);
67
76
  const workspaceId = normalizeRecordId(resolvedWorkspace?.id, { fallback: null });
68
77
  if (!workspaceId) {
69
78
  throw new AppError(409, "Workspace selection required.");
@@ -1,6 +1,5 @@
1
1
  import { AppError } from "@jskit-ai/kernel/server/runtime";
2
2
  import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
- import { resolveWorkspace } from "@jskit-ai/workspaces-core/server/support/resolveWorkspace";
4
3
  import { resolveWorkspaceSlug } from "@jskit-ai/assistant-core/server";
5
4
  import {
6
5
  ASSISTANT_STREAM_EVENT_TYPES
@@ -553,7 +552,8 @@ function createChatService({
553
552
  serviceToolCatalog,
554
553
  assistantConfigService,
555
554
  appConfig = {},
556
- resolveAppConfig = null
555
+ resolveAppConfig = null,
556
+ workspaceScopeSupport = null
557
557
  } = {}) {
558
558
  if (!aiClientFactory || typeof aiClientFactory.resolveClient !== "function" || !transcriptService || !serviceToolCatalog || !assistantConfigService) {
559
559
  throw new Error(
@@ -564,6 +564,18 @@ function createChatService({
564
564
  const resolveCurrentAppConfig =
565
565
  typeof resolveAppConfig === "function" ? resolveAppConfig : () => appConfig;
566
566
 
567
+ function resolveRuntimeWorkspace(assistantSurface, context = {}, input = {}) {
568
+ if (assistantSurface?.runtimeSurfaceRequiresWorkspace !== true) {
569
+ return null;
570
+ }
571
+
572
+ if (!workspaceScopeSupport || typeof workspaceScopeSupport.resolveWorkspace !== "function") {
573
+ throw new Error("assistant.chat.service requires workspace server scope support for workspace-scoped assistant surfaces.");
574
+ }
575
+
576
+ return workspaceScopeSupport.resolveWorkspace(context, input);
577
+ }
578
+
567
579
  async function streamChat(payload = {}, options = {}) {
568
580
  const assistantSurface = requireAssistantSurface(resolveCurrentAppConfig(), payload?.targetSurfaceId);
569
581
  const aiClient = aiClientFactory.resolveClient(assistantSurface.targetSurfaceId);
@@ -573,9 +585,7 @@ function createChatService({
573
585
 
574
586
  const context = normalizeObject(options.context);
575
587
  const assistantContext = buildAssistantActionContext(context, assistantSurface);
576
- const workspace = assistantSurface.runtimeSurfaceRequiresWorkspace
577
- ? resolveWorkspace(assistantContext, payload)
578
- : null;
588
+ const workspace = resolveRuntimeWorkspace(assistantSurface, assistantContext, payload);
579
589
  const source = normalizeStreamInput(payload);
580
590
  const streamWriter = options.streamWriter;
581
591
  if (!hasStreamWriter(streamWriter)) {
@@ -1010,9 +1020,7 @@ function createChatService({
1010
1020
  const assistantSurface = requireAssistantSurface(resolveCurrentAppConfig(), options?.input?.targetSurfaceId);
1011
1021
  const context = normalizeObject(options.context);
1012
1022
  const assistantContext = buildAssistantActionContext(context, assistantSurface);
1013
- const workspace = assistantSurface.runtimeSurfaceRequiresWorkspace
1014
- ? resolveWorkspace(assistantContext, options.input || {})
1015
- : null;
1023
+ const workspace = resolveRuntimeWorkspace(assistantSurface, assistantContext, options.input || {});
1016
1024
  return transcriptService.listConversationsForUser(assistantSurface, workspace, assistantContext.actor, query, {
1017
1025
  context: assistantContext
1018
1026
  });
@@ -1022,9 +1030,7 @@ function createChatService({
1022
1030
  const assistantSurface = requireAssistantSurface(resolveCurrentAppConfig(), options?.input?.targetSurfaceId);
1023
1031
  const context = normalizeObject(options.context);
1024
1032
  const assistantContext = buildAssistantActionContext(context, assistantSurface);
1025
- const workspace = assistantSurface.runtimeSurfaceRequiresWorkspace
1026
- ? resolveWorkspace(assistantContext, options.input || {})
1027
- : null;
1033
+ const workspace = resolveRuntimeWorkspace(assistantSurface, assistantContext, options.input || {});
1028
1034
  return transcriptService.getConversationMessagesForUser(
1029
1035
  assistantSurface,
1030
1036
  workspace,
@@ -0,0 +1,35 @@
1
+ const WORKSPACES_SERVER_SCOPE_SUPPORT_TOKEN = "workspaces.server.scope-support";
2
+
3
+ function isWorkspaceServerScopeSupport(value) {
4
+ return Boolean(
5
+ value &&
6
+ value.available === true &&
7
+ value.paramsValidator &&
8
+ typeof value.paramsValidator.normalize === "function" &&
9
+ typeof value.buildInputFromRouteParams === "function" &&
10
+ typeof value.resolveWorkspace === "function"
11
+ );
12
+ }
13
+
14
+ function resolveWorkspaceServerScopeSupport(scope = null, { required = false, caller = "assistant-runtime" } = {}) {
15
+ const support =
16
+ scope && typeof scope.has === "function" && typeof scope.make === "function" && scope.has(WORKSPACES_SERVER_SCOPE_SUPPORT_TOKEN)
17
+ ? scope.make(WORKSPACES_SERVER_SCOPE_SUPPORT_TOKEN)
18
+ : null;
19
+
20
+ if (isWorkspaceServerScopeSupport(support)) {
21
+ return support;
22
+ }
23
+
24
+ if (required) {
25
+ throw new Error(`${caller} requires ${WORKSPACES_SERVER_SCOPE_SUPPORT_TOKEN} for workspace-scoped assistant surfaces.`);
26
+ }
27
+
28
+ return null;
29
+ }
30
+
31
+ export {
32
+ WORKSPACES_SERVER_SCOPE_SUPPORT_TOKEN,
33
+ isWorkspaceServerScopeSupport,
34
+ resolveWorkspaceServerScopeSupport
35
+ };
@@ -23,6 +23,26 @@ function createAssistantAppConfig() {
23
23
  };
24
24
  }
25
25
 
26
+ function createWorkspaceServerScopeSupport() {
27
+ return Object.freeze({
28
+ available: true,
29
+ paramsValidator: Object.freeze({
30
+ normalize(value = {}) {
31
+ return {
32
+ workspaceSlug: String(value?.workspaceSlug || "").trim().toLowerCase()
33
+ };
34
+ }
35
+ }),
36
+ buildInputFromRouteParams(params = {}) {
37
+ const workspaceSlug = String(params?.workspaceSlug || "").trim().toLowerCase();
38
+ return workspaceSlug ? { workspaceSlug } : {};
39
+ },
40
+ resolveWorkspace(context = {}, input = {}) {
41
+ return input.workspace || context.workspace || null;
42
+ }
43
+ });
44
+ }
45
+
26
46
  test("registerRoutes resolves appConfig lazily when handlers run", async () => {
27
47
  const routes = [];
28
48
  let currentAppConfig = null;
@@ -41,10 +61,16 @@ test("registerRoutes resolves appConfig lazily when handlers run", async () => {
41
61
  if (token === "appConfig") {
42
62
  return currentAppConfig;
43
63
  }
64
+ if (token === "workspaces.server.scope-support") {
65
+ return createWorkspaceServerScopeSupport();
66
+ }
44
67
  throw new Error(`Unexpected token: ${token}`);
45
68
  },
46
69
  has(token) {
47
- return token === "appConfig" ? Boolean(currentAppConfig) : token === "jskit.http.router";
70
+ return (
71
+ (token === "appConfig" ? Boolean(currentAppConfig) : token === "jskit.http.router") ||
72
+ token === "workspaces.server.scope-support"
73
+ );
48
74
  }
49
75
  };
50
76
 
@@ -126,10 +152,16 @@ test("registerRoutes returns clear AppError payload for pre-stream assistant fai
126
152
  if (token === "appConfig") {
127
153
  return currentAppConfig;
128
154
  }
155
+ if (token === "workspaces.server.scope-support") {
156
+ return createWorkspaceServerScopeSupport();
157
+ }
129
158
  throw new Error(`Unexpected token: ${token}`);
130
159
  },
131
160
  has(token) {
132
- return token === "appConfig" ? Boolean(currentAppConfig) : token === "jskit.http.router";
161
+ return (
162
+ (token === "appConfig" ? Boolean(currentAppConfig) : token === "jskit.http.router") ||
163
+ token === "workspaces.server.scope-support"
164
+ );
133
165
  }
134
166
  };
135
167
 
@@ -216,7 +248,8 @@ test("chat service resolves appConfig lazily when conversations are listed", asy
216
248
  },
217
249
  serviceToolCatalog: {},
218
250
  assistantConfigService: {},
219
- resolveAppConfig: () => currentAppConfig
251
+ resolveAppConfig: () => currentAppConfig,
252
+ workspaceScopeSupport: createWorkspaceServerScopeSupport()
220
253
  });
221
254
 
222
255
  currentAppConfig = createAssistantAppConfig();
@@ -246,3 +279,76 @@ test("chat service resolves appConfig lazily when conversations are listed", asy
246
279
  assert.equal(response.assistantSurface.targetSurfaceId, "admin");
247
280
  assert.equal(response.workspace.slug, "dogandgroom");
248
281
  });
282
+
283
+ test("chat service rejects workspace-scoped assistant surfaces when workspace support is unavailable", async () => {
284
+ const chatService = createChatService({
285
+ aiClientFactory: {
286
+ resolveClient() {
287
+ throw new Error("resolveClient should not be called when listing conversations.");
288
+ }
289
+ },
290
+ transcriptService: {
291
+ async listConversationsForUser() {
292
+ throw new Error("listConversationsForUser should not be called without workspace support.");
293
+ }
294
+ },
295
+ serviceToolCatalog: {},
296
+ assistantConfigService: {},
297
+ resolveAppConfig: () => createAssistantAppConfig()
298
+ });
299
+
300
+ await assert.rejects(
301
+ () =>
302
+ chatService.listConversations(
303
+ {
304
+ limit: 20
305
+ },
306
+ {
307
+ input: {
308
+ targetSurfaceId: "admin",
309
+ workspaceSlug: "dogandgroom"
310
+ },
311
+ context: {
312
+ actor: {
313
+ authenticated: true,
314
+ userId: 42
315
+ }
316
+ }
317
+ }
318
+ ),
319
+ /workspace server scope support/
320
+ );
321
+ });
322
+
323
+ test("registerRoutes omits workspace assistant routes when workspace scope support is unavailable", () => {
324
+ const routes = [];
325
+ const app = {
326
+ make(token) {
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
+ };
343
+
344
+ registerRoutes(app);
345
+
346
+ assert.equal(
347
+ routes.some((entry) => entry.path.startsWith("/api/w/:workspaceSlug/assistant/")),
348
+ false
349
+ );
350
+ assert.equal(
351
+ routes.some((entry) => entry.path.startsWith("/api/assistant/")),
352
+ true
353
+ );
354
+ });
@@ -15,6 +15,10 @@ function findFileMutation(id) {
15
15
  test("assistant-runtime descriptor registers runtime providers and initializes assistant config roots", () => {
16
16
  assert.equal(descriptor.kind, "runtime");
17
17
  assert.equal(descriptor.packageId, "@jskit-ai/assistant-runtime");
18
+ assert.equal(descriptor.dependsOn.includes("@jskit-ai/workspaces-core"), false);
19
+ assert.equal(descriptor.dependsOn.includes("@jskit-ai/workspaces-web"), false);
20
+ assert.equal(descriptor.capabilities?.requires?.includes("workspaces.core"), false);
21
+ assert.equal(descriptor.capabilities?.requires?.includes("workspaces.web"), false);
18
22
  assert.equal(descriptor.runtime?.server?.providers?.[0]?.entrypoint, "src/server/AssistantProvider.js");
19
23
  assert.equal(descriptor.runtime?.client?.providers?.[0]?.entrypoint, "src/client/providers/AssistantClientProvider.js");
20
24