@jskit-ai/assistant-core 0.1.5 → 0.1.7

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-core",
4
- version: "0.1.5",
4
+ version: "0.1.7",
5
5
  kind: "runtime",
6
6
  description: "Reusable assistant client/server/shared primitives without surface-specific routes or settings ownership.",
7
7
  dependsOn: [
@@ -45,9 +45,9 @@ export default Object.freeze({
45
45
  mutations: {
46
46
  dependencies: {
47
47
  runtime: {
48
- "@jskit-ai/http-runtime": "0.1.28",
49
- "@jskit-ai/kernel": "0.1.29",
50
- "@jskit-ai/users-core": "0.1.39",
48
+ "@jskit-ai/http-runtime": "0.1.30",
49
+ "@jskit-ai/kernel": "0.1.31",
50
+ "@jskit-ai/users-core": "0.1.41",
51
51
  "@tanstack/vue-query": "^5.90.5",
52
52
  "dompurify": "^3.3.3",
53
53
  "marked": "^17.0.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/assistant-core",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -11,9 +11,9 @@
11
11
  "./shared": "./src/shared/index.js"
12
12
  },
13
13
  "dependencies": {
14
- "@jskit-ai/http-runtime": "0.1.28",
15
- "@jskit-ai/kernel": "0.1.29",
16
- "@jskit-ai/users-core": "0.1.39",
14
+ "@jskit-ai/http-runtime": "0.1.30",
15
+ "@jskit-ai/kernel": "0.1.31",
16
+ "@jskit-ai/users-core": "0.1.41",
17
17
  "@tanstack/vue-query": "^5.90.5",
18
18
  "dompurify": "^3.3.3",
19
19
  "marked": "^17.0.4",
@@ -130,6 +130,30 @@ function createAssistantApi({ request, requestStream, resolveBasePath, resolveSu
130
130
  appendQueryString(`${basePath}/conversations/${encodedConversationId}/messages`, params.toString()),
131
131
  requestHeaders ? { headers: requestHeaders } : {}
132
132
  );
133
+ },
134
+
135
+ getSettings() {
136
+ const basePath = resolveRequiredBasePath(resolveBasePath);
137
+ const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
138
+
139
+ return request(
140
+ `${basePath}/settings`,
141
+ requestHeaders ? { headers: requestHeaders } : {}
142
+ );
143
+ },
144
+
145
+ updateSettings(payload = {}) {
146
+ const basePath = resolveRequiredBasePath(resolveBasePath);
147
+ const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
148
+
149
+ return request(
150
+ `${basePath}/settings`,
151
+ {
152
+ method: "PATCH",
153
+ ...(requestHeaders ? { headers: requestHeaders } : {}),
154
+ body: payload
155
+ }
156
+ );
133
157
  }
134
158
  });
135
159
  }
@@ -1,6 +1,7 @@
1
1
  import { requireAuth } from "@jskit-ai/kernel/server/runtime";
2
2
  import { resolveActionContributors } from "@jskit-ai/kernel/server/actions";
3
3
  import { normalizeActionDefinition } from "@jskit-ai/kernel/shared/actions";
4
+ import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
4
5
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
5
6
  import { resolveWorkspaceSlug } from "./resolveWorkspaceSlug.js";
6
7
 
@@ -209,6 +210,34 @@ function hasAutomationChannel(action = {}) {
209
210
  return channels.some((channel) => normalizeText(channel).toLowerCase() === AUTOMATION_CHANNEL);
210
211
  }
211
212
 
213
+ function normalizeSurfaceList(value) {
214
+ const source = Array.isArray(value) ? value : [value];
215
+ const resolved = [];
216
+
217
+ for (const entry of source) {
218
+ const normalized = normalizeSurfaceId(entry);
219
+ if (normalized) {
220
+ resolved.push(normalized);
221
+ }
222
+ }
223
+
224
+ return Object.freeze(resolved);
225
+ }
226
+
227
+ function canUseToolOnSurface(entry = {}, context = {}) {
228
+ const toolSurfaces = Array.isArray(entry.surfaces) ? entry.surfaces : [];
229
+ if (toolSurfaces.length < 1) {
230
+ return true;
231
+ }
232
+
233
+ const contextSurfaceId = normalizeSurfaceId(context?.surface);
234
+ if (!contextSurfaceId) {
235
+ return true;
236
+ }
237
+
238
+ return toolSurfaces.includes(contextSurfaceId);
239
+ }
240
+
212
241
  function resolveActionBackedToolEntries(scope) {
213
242
  if (!scope || typeof scope.has !== "function" || typeof scope.make !== "function") {
214
243
  return new Map();
@@ -261,7 +290,8 @@ function resolveActionBackedToolEntries(scope) {
261
290
  description: assistantExtension.description || `Run ${actionId}.`,
262
291
  inputSchema,
263
292
  outputSchema,
264
- permission: normalizePermissionSpec(normalizedAction.permission)
293
+ permission: normalizePermissionSpec(normalizedAction.permission),
294
+ surfaces: normalizeSurfaceList(normalizedAction.surfaces)
265
295
  });
266
296
  const existing = entriesByActionId.get(actionKey);
267
297
  if (!existing || actionVersion >= Number(existing.actionVersion || 0)) {
@@ -309,7 +339,8 @@ function resolveActionToolEntries(
309
339
  parameters: actionEntry.inputSchema,
310
340
  outputSchema: actionEntry.outputSchema
311
341
  }),
312
- permission: actionEntry.permission
342
+ permission: actionEntry.permission,
343
+ surfaces: actionEntry.surfaces
313
344
  })
314
345
  );
315
346
  }
@@ -346,6 +377,9 @@ function createServiceToolCatalog(
346
377
  const tools = [];
347
378
  const byName = new Map();
348
379
  for (const entry of resolveOrCreateMethodEntries()) {
380
+ if (!canUseToolOnSurface(entry, context)) {
381
+ continue;
382
+ }
349
383
  if (!canInvokeMethod(entry.permission, context)) {
350
384
  continue;
351
385
  }
@@ -3,30 +3,38 @@ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
3
  import { resolveApiBasePath } from "@jskit-ai/users-core/shared/support/usersApiPaths";
4
4
 
5
5
  const ASSISTANT_API_RELATIVE_PATH = "/assistant";
6
- const ASSISTANT_SETTINGS_API_RELATIVE_PATH = "/assistant/settings";
6
+ const ASSISTANT_SETTINGS_API_RELATIVE_PATH = "/assistant/:surfaceId/settings";
7
7
  const ASSISTANT_WORKSPACE_API_BASE_PATH_TEMPLATE = "/api/w/:workspaceSlug/assistant";
8
8
  const ASSISTANT_PUBLIC_API_BASE_PATH = "/api/assistant";
9
- const ASSISTANT_WORKSPACE_SETTINGS_API_PATH_TEMPLATE = "/api/w/:workspaceSlug/assistant/settings";
10
- const ASSISTANT_PUBLIC_SETTINGS_API_PATH = "/api/assistant/settings";
9
+ const ASSISTANT_WORKSPACE_SETTINGS_API_PATH_TEMPLATE = "/api/w/:workspaceSlug/assistant/:surfaceId/settings";
10
+ const ASSISTANT_PUBLIC_SETTINGS_API_PATH = "/api/assistant/:surfaceId/settings";
11
11
 
12
- function materializeAssistantPath(basePath = "", workspaceSlug = "", suffix = "/") {
12
+ function materializeAssistantPath(basePath = "", { workspaceSlug = "", surfaceId = "", suffix = "/" } = {}) {
13
13
  const normalizedBasePath = String(basePath || "").trim();
14
14
  if (!normalizedBasePath) {
15
15
  return "";
16
16
  }
17
17
 
18
- const normalizedSuffix = normalizePathname(suffix);
18
+ let materializedBasePath = normalizedBasePath;
19
19
  if (normalizedBasePath.includes(":workspaceSlug")) {
20
20
  const normalizedWorkspaceSlug = normalizeText(workspaceSlug).toLowerCase();
21
21
  if (!normalizedWorkspaceSlug) {
22
22
  return "";
23
23
  }
24
24
 
25
- const materializedBasePath = normalizedBasePath.replace(":workspaceSlug", encodeURIComponent(normalizedWorkspaceSlug));
26
- return normalizedSuffix === "/" ? materializedBasePath : `${materializedBasePath}${normalizedSuffix}`;
25
+ materializedBasePath = normalizedBasePath.replace(":workspaceSlug", encodeURIComponent(normalizedWorkspaceSlug));
26
+ }
27
+ if (materializedBasePath.includes(":surfaceId")) {
28
+ const normalizedSurfaceId = normalizeText(surfaceId).toLowerCase();
29
+ if (!normalizedSurfaceId) {
30
+ return "";
31
+ }
32
+
33
+ materializedBasePath = materializedBasePath.replace(":surfaceId", encodeURIComponent(normalizedSurfaceId));
27
34
  }
28
35
 
29
- return normalizedSuffix === "/" ? normalizedBasePath : `${normalizedBasePath}${normalizedSuffix}`;
36
+ const normalizedSuffix = normalizePathname(suffix);
37
+ return normalizedSuffix === "/" ? materializedBasePath : `${materializedBasePath}${normalizedSuffix}`;
30
38
  }
31
39
 
32
40
  function resolveAssistantApiBasePath({ requiresWorkspace = false } = {}) {
@@ -48,18 +56,23 @@ function buildAssistantApiPath({ requiresWorkspace = false, workspaceSlug = "",
48
56
  resolveAssistantApiBasePath({
49
57
  requiresWorkspace
50
58
  }),
51
- workspaceSlug,
52
- suffix
59
+ {
60
+ workspaceSlug,
61
+ suffix
62
+ }
53
63
  );
54
64
  }
55
65
 
56
- function buildAssistantSettingsApiPath({ requiresWorkspace = false, workspaceSlug = "", suffix = "/" } = {}) {
66
+ function buildAssistantSettingsApiPath({ requiresWorkspace = false, workspaceSlug = "", surfaceId = "", suffix = "/" } = {}) {
57
67
  return materializeAssistantPath(
58
68
  resolveAssistantSettingsApiPath({
59
69
  requiresWorkspace
60
70
  }),
61
- workspaceSlug,
62
- suffix
71
+ {
72
+ workspaceSlug,
73
+ surfaceId,
74
+ suffix
75
+ }
63
76
  );
64
77
  }
65
78
 
@@ -11,8 +11,8 @@ test("assistant path helpers derive workspace and non-workspace API bases from s
11
11
  assert.equal(resolveAssistantApiBasePath({ requiresWorkspace: false }), "/api/assistant");
12
12
  assert.equal(resolveAssistantApiBasePath({ requiresWorkspace: true }), "/api/w/:workspaceSlug/assistant");
13
13
 
14
- assert.equal(resolveAssistantSettingsApiPath({ requiresWorkspace: false }), "/api/assistant/settings");
15
- assert.equal(resolveAssistantSettingsApiPath({ requiresWorkspace: true }), "/api/w/:workspaceSlug/assistant/settings");
14
+ assert.equal(resolveAssistantSettingsApiPath({ requiresWorkspace: false }), "/api/assistant/:surfaceId/settings");
15
+ assert.equal(resolveAssistantSettingsApiPath({ requiresWorkspace: true }), "/api/w/:workspaceSlug/assistant/:surfaceId/settings");
16
16
  });
17
17
 
18
18
  test("assistant path builders materialize workspace-aware and public API paths", () => {
@@ -36,16 +36,17 @@ test("assistant path builders materialize workspace-aware and public API paths",
36
36
  assert.equal(
37
37
  buildAssistantSettingsApiPath({
38
38
  requiresWorkspace: false,
39
- suffix: "/"
39
+ surfaceId: "admin"
40
40
  }),
41
- "/api/assistant/settings"
41
+ "/api/assistant/admin/settings"
42
42
  );
43
43
 
44
44
  assert.equal(
45
45
  buildAssistantSettingsApiPath({
46
46
  requiresWorkspace: true,
47
- workspaceSlug: "acme"
47
+ workspaceSlug: "acme",
48
+ surfaceId: "admin"
48
49
  }),
49
- "/api/w/acme/assistant/settings"
50
+ "/api/w/acme/assistant/admin/settings"
50
51
  );
51
52
  });
@@ -1233,3 +1233,82 @@ test("service tool catalog executes action-backed tools with object payloads", a
1233
1233
  }
1234
1234
  });
1235
1235
  });
1236
+
1237
+ test("service tool catalog hides automation actions from other surfaces", () => {
1238
+ const app = createApp();
1239
+ const actionRuntimeProvider = new ActionRuntimeServiceProvider();
1240
+ actionRuntimeProvider.register(app);
1241
+
1242
+ app.actions([
1243
+ {
1244
+ id: "demo.admin.list",
1245
+ domain: "demo",
1246
+ version: 1,
1247
+ kind: "query",
1248
+ channels: ["automation"],
1249
+ surfaces: ["admin"],
1250
+ permission: {
1251
+ require: "authenticated"
1252
+ },
1253
+ inputValidator: {
1254
+ schema: Type.Object({}, { additionalProperties: false })
1255
+ },
1256
+ outputValidator: {
1257
+ schema: Type.Array(Type.Object({}, { additionalProperties: true }))
1258
+ },
1259
+ idempotency: "none",
1260
+ audit: {
1261
+ actionName: "demo.admin.list"
1262
+ },
1263
+ observability: {},
1264
+ async execute() {
1265
+ return [];
1266
+ }
1267
+ },
1268
+ {
1269
+ id: "demo.console.list",
1270
+ domain: "demo",
1271
+ version: 1,
1272
+ kind: "query",
1273
+ channels: ["automation"],
1274
+ surfaces: ["console"],
1275
+ permission: {
1276
+ require: "authenticated"
1277
+ },
1278
+ inputValidator: {
1279
+ schema: Type.Object({}, { additionalProperties: false })
1280
+ },
1281
+ outputValidator: {
1282
+ schema: Type.Array(Type.Object({}, { additionalProperties: true }))
1283
+ },
1284
+ idempotency: "none",
1285
+ audit: {
1286
+ actionName: "demo.console.list"
1287
+ },
1288
+ observability: {},
1289
+ async execute() {
1290
+ return [];
1291
+ }
1292
+ }
1293
+ ]);
1294
+
1295
+ const catalog = createServiceToolCatalog(app, {
1296
+ skipActionPrefixes: []
1297
+ });
1298
+
1299
+ const adminToolSet = catalog.resolveToolSet({
1300
+ actor: { id: 1 },
1301
+ permissions: [],
1302
+ channel: "internal",
1303
+ surface: "admin"
1304
+ });
1305
+ const consoleToolSet = catalog.resolveToolSet({
1306
+ actor: { id: 1 },
1307
+ permissions: [],
1308
+ channel: "internal",
1309
+ surface: "console"
1310
+ });
1311
+
1312
+ assert.deepEqual(adminToolSet.tools.map((entry) => entry.actionId), ["demo.admin.list"]);
1313
+ assert.deepEqual(consoleToolSet.tools.map((entry) => entry.actionId), ["demo.console.list"]);
1314
+ });