@jskit-ai/assistant 0.1.32 → 0.1.35

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 (76) hide show
  1. package/package.descriptor.mjs +346 -142
  2. package/package.json +3 -19
  3. package/src/server/buildTemplateContext.js +107 -0
  4. package/templates/migrations/assistant_config_initial.cjs +25 -0
  5. package/templates/migrations/assistant_transcripts_initial.cjs +21 -14
  6. package/templates/src/local-package/client/components/AssistantSettingsClientElement.vue +88 -0
  7. package/templates/src/local-package/client/components/AssistantSurfaceClientElement.vue +10 -0
  8. package/{src/client/composables/useAssistantWorkspaceRuntime.js → templates/src/local-package/client/composables/useAssistantRuntime.js} +91 -114
  9. package/templates/src/local-package/client/index.js +3 -0
  10. package/templates/src/local-package/client/providers/AssistantClientProvider.js +16 -0
  11. package/templates/src/local-package/package.descriptor.mjs +85 -0
  12. package/templates/src/local-package/package.json +11 -0
  13. package/{src/server/AssistantServiceProvider.js → templates/src/local-package/server/AssistantProvider.js} +37 -61
  14. package/templates/src/local-package/server/actionIds.js +9 -0
  15. package/templates/src/local-package/server/actions.js +190 -0
  16. package/templates/src/local-package/server/registerRoutes.js +296 -0
  17. package/templates/src/local-package/server/repositories/assistantConfigRepository.js +141 -0
  18. package/{src → templates/src/local-package}/server/repositories/conversationsRepository.js +44 -45
  19. package/{src → templates/src/local-package}/server/repositories/messagesRepository.js +49 -34
  20. package/templates/src/local-package/server/services/assistantConfigService.js +90 -0
  21. package/{src → templates/src/local-package}/server/services/chatService.js +45 -37
  22. package/{src → templates/src/local-package}/server/services/transcriptService.js +61 -82
  23. package/templates/src/local-package/shared/assistantRuntimeConfig.js +13 -0
  24. package/templates/src/local-package/shared/index.js +1 -0
  25. package/templates/src/pages/assistant/index.vue +7 -0
  26. package/test/buildTemplateContext.test.js +112 -0
  27. package/test/packageDescriptor.test.js +69 -0
  28. package/src/client/components/AssistantClientElement.vue +0 -1316
  29. package/src/client/components/AssistantConsoleSettingsClientElement.vue +0 -70
  30. package/src/client/components/AssistantSettingsFormCard.vue +0 -76
  31. package/src/client/components/AssistantWorkspaceClientElement.vue +0 -15
  32. package/src/client/components/AssistantWorkspaceSettingsClientElement.vue +0 -72
  33. package/src/client/index.js +0 -10
  34. package/src/client/lib/assistantApi.js +0 -137
  35. package/src/client/lib/assistantHttpClient.js +0 -10
  36. package/src/client/lib/markdownRenderer.js +0 -31
  37. package/src/client/providers/AssistantWebClientProvider.js +0 -20
  38. package/src/server/actionIds.js +0 -11
  39. package/src/server/actions.js +0 -191
  40. package/src/server/lib/aiClient.js +0 -43
  41. package/src/server/lib/ndjson.js +0 -47
  42. package/src/server/lib/providers/anthropicClient.js +0 -375
  43. package/src/server/lib/providers/common.js +0 -150
  44. package/src/server/lib/providers/deepSeekClient.js +0 -22
  45. package/src/server/lib/providers/openAiClient.js +0 -13
  46. package/src/server/lib/providers/openAiCompatibleClient.js +0 -69
  47. package/src/server/lib/resolveWorkspaceSlug.js +0 -24
  48. package/src/server/lib/serviceToolCatalog.js +0 -459
  49. package/src/server/registerRoutes.js +0 -383
  50. package/src/server/repositories/assistantSettingsRepository.js +0 -100
  51. package/src/server/repositories/repositoryPersistenceUtils.js +0 -48
  52. package/src/server/services/assistantSettingsService.js +0 -149
  53. package/src/shared/assistantPaths.js +0 -50
  54. package/src/shared/assistantResource.js +0 -317
  55. package/src/shared/assistantSettingsResource.js +0 -197
  56. package/src/shared/index.js +0 -43
  57. package/src/shared/queryKeys.js +0 -69
  58. package/src/shared/settingsEvents.js +0 -6
  59. package/src/shared/streamEvents.js +0 -29
  60. package/src/shared/support/conversationStatus.js +0 -18
  61. package/src/shared/support/jsonObject.js +0 -18
  62. package/src/shared/support/positiveInteger.js +0 -9
  63. package/templates/migrations/assistant_settings_initial.cjs +0 -37
  64. package/templates/src/pages/admin/workspace/assistant/index.vue +0 -7
  65. package/test/aiConfigValidation.test.js +0 -15
  66. package/test/assistantApiSurfaceHeader.test.js +0 -64
  67. package/test/assistantResource.test.js +0 -53
  68. package/test/assistantSettingsResource.test.js +0 -48
  69. package/test/assistantSettingsService.test.js +0 -133
  70. package/test/chatService.test.js +0 -841
  71. package/test/descriptorSurfaceOption.test.js +0 -35
  72. package/test/queryKeys.test.js +0 -41
  73. package/test/resolveWorkspaceSlug.test.js +0 -83
  74. package/test/routeInputContracts.test.js +0 -286
  75. package/test/serviceToolCatalog.test.js +0 -1235
  76. package/test/transcriptService.test.js +0 -175
@@ -0,0 +1,107 @@
1
+ import path from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ import {
4
+ loadAppConfigFromModuleUrl,
5
+ resolveShellOutletPlacementTargetFromApp
6
+ } from "@jskit-ai/kernel/server/support";
7
+ import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
8
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
9
+ import { toSnakeCase } from "@jskit-ai/kernel/shared/support/stringCase";
10
+
11
+ const DEFAULT_MENU_COMPONENT_TOKEN = "users.web.shell.surface-aware-menu-link-item";
12
+
13
+ function normalizeConfigScope(value = "") {
14
+ const normalized = normalizeText(value).toLowerCase();
15
+ if (normalized === "global" || normalized === "workspace") {
16
+ return normalized;
17
+ }
18
+
19
+ throw new Error('assistant generator option "config-scope" must be "global" or "workspace".');
20
+ }
21
+
22
+ async function loadAppConfig(appRoot = "") {
23
+ const publicConfigUrl = pathToFileURL(path.join(appRoot, "config", "public.js")).href;
24
+ return loadAppConfigFromModuleUrl({
25
+ moduleUrl: publicConfigUrl
26
+ });
27
+ }
28
+
29
+ function resolveSurfaceDefinition(appConfig = {}, surfaceId = "", optionName = "surface") {
30
+ const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
31
+ if (!normalizedSurfaceId) {
32
+ throw new Error(`assistant generator requires --${optionName}.`);
33
+ }
34
+
35
+ const sourceDefinitions = appConfig && typeof appConfig.surfaceDefinitions === "object" && !Array.isArray(appConfig.surfaceDefinitions)
36
+ ? appConfig.surfaceDefinitions
37
+ : {};
38
+ const rawDefinition = sourceDefinitions[normalizedSurfaceId];
39
+ if (!rawDefinition || typeof rawDefinition !== "object" || Array.isArray(rawDefinition)) {
40
+ throw new Error(`assistant generator surface "${normalizedSurfaceId}" is not defined in config/public.js.`);
41
+ }
42
+ if (rawDefinition.enabled === false) {
43
+ throw new Error(`assistant generator surface "${normalizedSurfaceId}" is disabled in config/public.js.`);
44
+ }
45
+
46
+ return Object.freeze({
47
+ id: normalizedSurfaceId,
48
+ requiresWorkspace: rawDefinition.requiresWorkspace === true,
49
+ accessPolicyId: normalizeText(rawDefinition.accessPolicyId).toLowerCase()
50
+ });
51
+ }
52
+
53
+ function resolveTableSuffix(surfaceId = "") {
54
+ return toSnakeCase(surfaceId) || "assistant";
55
+ }
56
+
57
+ async function resolveMenuPlacementTarget(appRoot = "", options = {}) {
58
+ return resolveShellOutletPlacementTargetFromApp({
59
+ appRoot,
60
+ context: "assistant-generator",
61
+ placement: normalizeText(options?.placement)
62
+ });
63
+ }
64
+
65
+ async function buildTemplateContext({ appRoot, options } = {}) {
66
+ const appConfig = await loadAppConfig(appRoot);
67
+ const runtimeSurface = resolveSurfaceDefinition(appConfig, options?.["runtime-surface"], "runtime-surface");
68
+ const settingsSurface = resolveSurfaceDefinition(appConfig, options?.["settings-surface"], "settings-surface");
69
+ const configScope = normalizeConfigScope(options?.["config-scope"]);
70
+
71
+ if (configScope === "workspace" && runtimeSurface.requiresWorkspace !== true) {
72
+ throw new Error(
73
+ `assistant generator config-scope "workspace" requires runtime surface "${runtimeSurface.id}" with requiresWorkspace=true.`
74
+ );
75
+ }
76
+ if (configScope === "workspace" && settingsSurface.requiresWorkspace !== true) {
77
+ throw new Error(
78
+ `assistant generator config-scope "workspace" requires settings surface "${settingsSurface.id}" with requiresWorkspace=true.`
79
+ );
80
+ }
81
+
82
+ const placementTarget = await resolveMenuPlacementTarget(appRoot, options);
83
+ const tableSuffix = resolveTableSuffix(runtimeSurface.id);
84
+ const menuComponentToken = normalizeText(options?.["placement-component-token"]) || DEFAULT_MENU_COMPONENT_TOKEN;
85
+
86
+ return Object.freeze({
87
+ "__ASSISTANT_RUNTIME_SURFACE_ID__": runtimeSurface.id,
88
+ "__ASSISTANT_SETTINGS_SURFACE_ID__": settingsSurface.id,
89
+ "__ASSISTANT_RUNTIME_SURFACE_REQUIRES_WORKSPACE__": runtimeSurface.requiresWorkspace ? "true" : "false",
90
+ "__ASSISTANT_SETTINGS_SURFACE_REQUIRES_WORKSPACE__": settingsSurface.requiresWorkspace ? "true" : "false",
91
+ "__ASSISTANT_SETTINGS_SURFACE_REQUIRES_CONSOLE_OWNER__":
92
+ settingsSurface.accessPolicyId === "console_owner" ? "true" : "false",
93
+ "__ASSISTANT_CONFIG_SCOPE__": configScope,
94
+ "__ASSISTANT_SETTINGS_HOST__": `${settingsSurface.id}-settings`,
95
+ "__ASSISTANT_CONFIG_TABLE__": "assistant_config",
96
+ "__ASSISTANT_CONVERSATIONS_TABLE__": `assistant_${tableSuffix}_conversations`,
97
+ "__ASSISTANT_MESSAGES_TABLE__": `assistant_${tableSuffix}_messages`,
98
+ "__ASSISTANT_MENU_PLACEMENT_HOST__": placementTarget.host,
99
+ "__ASSISTANT_MENU_PLACEMENT_POSITION__": placementTarget.position,
100
+ "__ASSISTANT_MENU_COMPONENT_TOKEN__": menuComponentToken,
101
+ "__ASSISTANT_MENU_LABEL__": normalizeText(options?.["menu-label"]) || "Assistant",
102
+ "__ASSISTANT_MENU_WORKSPACE_SUFFIX__": "/assistant",
103
+ "__ASSISTANT_MENU_NON_WORKSPACE_SUFFIX__": "/assistant"
104
+ });
105
+ }
106
+
107
+ export { buildTemplateContext };
@@ -0,0 +1,25 @@
1
+ exports.up = async function up(knex) {
2
+ const hasTable = await knex.schema.hasTable("__ASSISTANT_CONFIG_TABLE__");
3
+ if (!hasTable) {
4
+ await knex.schema.createTable("__ASSISTANT_CONFIG_TABLE__", (table) => {
5
+ table.increments("id").unsigned().primary();
6
+ table.string("target_surface_id", 64).notNullable();
7
+ table.string("scope_key", 160).notNullable();
8
+ table.integer("workspace_id").unsigned().nullable();
9
+ if ("__ASSISTANT_CONFIG_SCOPE__" === "workspace") {
10
+ table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
11
+ }
12
+ table.text("system_prompt").notNullable().defaultTo("");
13
+ table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
14
+ table.timestamp("updated_at").notNullable().defaultTo(knex.fn.now());
15
+
16
+ table.unique(["target_surface_id", "scope_key"], "uq___ASSISTANT_CONFIG_TABLE___target_surface_scope");
17
+ table.index(["target_surface_id"], "idx___ASSISTANT_CONFIG_TABLE___target_surface");
18
+ table.index(["workspace_id"], "idx___ASSISTANT_CONFIG_TABLE___workspace");
19
+ });
20
+ }
21
+ };
22
+
23
+ exports.down = async function down(knex) {
24
+ await knex.schema.dropTableIfExists("__ASSISTANT_CONFIG_TABLE__");
25
+ };
@@ -1,15 +1,18 @@
1
1
  exports.up = async function up(knex) {
2
- const hasConversationsTable = await knex.schema.hasTable("ai_conversations");
2
+ const hasConversationsTable = await knex.schema.hasTable("__ASSISTANT_CONVERSATIONS_TABLE__");
3
3
  if (!hasConversationsTable) {
4
- await knex.schema.createTable("ai_conversations", (table) => {
4
+ await knex.schema.createTable("__ASSISTANT_CONVERSATIONS_TABLE__", (table) => {
5
5
  table.increments("id").unsigned().primary();
6
- table.integer("workspace_id").unsigned().notNullable().references("id").inTable("workspaces").onDelete("CASCADE").index();
6
+ table.integer("workspace_id").unsigned().nullable();
7
+ if (__ASSISTANT_RUNTIME_SURFACE_REQUIRES_WORKSPACE__) {
8
+ table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
9
+ }
7
10
  table.integer("created_by_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL").index();
8
11
  table.string("title", 160).notNullable().defaultTo("New conversation");
9
12
  table.string("status", 32).notNullable().defaultTo("active");
10
13
  table.string("provider", 64).notNullable().defaultTo("");
11
14
  table.string("model", 128).notNullable().defaultTo("");
12
- table.string("surface_sid", 32).notNullable().defaultTo("admin");
15
+ table.string("surface_id", 32).notNullable().defaultTo("__ASSISTANT_RUNTIME_SURFACE_ID__");
13
16
  table.integer("message_count").unsigned().notNullable().defaultTo(0);
14
17
  table.text("metadata_json").nullable();
15
18
  table.timestamp("started_at").notNullable().defaultTo(knex.fn.now());
@@ -17,17 +20,20 @@ exports.up = async function up(knex) {
17
20
  table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
18
21
  table.timestamp("updated_at").notNullable().defaultTo(knex.fn.now());
19
22
 
20
- table.index(["workspace_id", "started_at"], "idx_ai_conversations_workspace_started_at");
21
- table.index(["workspace_id", "created_by_user_id"], "idx_ai_conversations_workspace_creator");
23
+ table.index(["workspace_id", "started_at"], "idx___ASSISTANT_CONVERSATIONS_TABLE___workspace_started_at");
24
+ table.index(["created_by_user_id", "started_at"], "idx___ASSISTANT_CONVERSATIONS_TABLE___creator_started_at");
22
25
  });
23
26
  }
24
27
 
25
- const hasMessagesTable = await knex.schema.hasTable("ai_messages");
28
+ const hasMessagesTable = await knex.schema.hasTable("__ASSISTANT_MESSAGES_TABLE__");
26
29
  if (!hasMessagesTable) {
27
- await knex.schema.createTable("ai_messages", (table) => {
30
+ await knex.schema.createTable("__ASSISTANT_MESSAGES_TABLE__", (table) => {
28
31
  table.increments("id").unsigned().primary();
29
- table.integer("conversation_id").unsigned().notNullable().references("id").inTable("ai_conversations").onDelete("CASCADE");
30
- table.integer("workspace_id").unsigned().notNullable().references("id").inTable("workspaces").onDelete("CASCADE").index();
32
+ table.integer("conversation_id").unsigned().notNullable().references("id").inTable("__ASSISTANT_CONVERSATIONS_TABLE__").onDelete("CASCADE");
33
+ table.integer("workspace_id").unsigned().nullable();
34
+ if (__ASSISTANT_RUNTIME_SURFACE_REQUIRES_WORKSPACE__) {
35
+ table.foreign("workspace_id").references("id").inTable("workspaces").onDelete("CASCADE");
36
+ }
31
37
  table.integer("seq").unsigned().notNullable();
32
38
  table.string("role", 32).notNullable();
33
39
  table.string("kind", 32).notNullable().defaultTo("chat");
@@ -37,13 +43,14 @@ exports.up = async function up(knex) {
37
43
  table.text("metadata_json").nullable();
38
44
  table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
39
45
 
40
- table.unique(["conversation_id", "seq"], "uq_ai_messages_conversation_seq");
41
- table.index(["conversation_id", "created_at"], "idx_ai_messages_conversation_created_at");
46
+ table.unique(["conversation_id", "seq"], "uq___ASSISTANT_MESSAGES_TABLE___conversation_seq");
47
+ table.index(["conversation_id", "created_at"], "idx___ASSISTANT_MESSAGES_TABLE___conversation_created_at");
48
+ table.index(["workspace_id"], "idx___ASSISTANT_MESSAGES_TABLE___workspace");
42
49
  });
43
50
  }
44
51
  };
45
52
 
46
53
  exports.down = async function down(knex) {
47
- await knex.schema.dropTableIfExists("ai_messages");
48
- await knex.schema.dropTableIfExists("ai_conversations");
54
+ await knex.schema.dropTableIfExists("__ASSISTANT_MESSAGES_TABLE__");
55
+ await knex.schema.dropTableIfExists("__ASSISTANT_CONVERSATIONS_TABLE__");
49
56
  };
@@ -0,0 +1,88 @@
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="addEdit.fieldErrors.systemPrompt ? [addEdit.fieldErrors.systemPrompt] : []"
20
+ />
21
+ </AssistantSettingsFormCard>
22
+ </template>
23
+
24
+ <script setup>
25
+ import { computed, reactive } from "vue";
26
+ import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
27
+ import { AssistantSettingsFormCard } from "@jskit-ai/assistant-core/client";
28
+ import {
29
+ assistantConfigResource,
30
+ assistantSettingsQueryKey
31
+ } from "@jskit-ai/assistant-core/shared";
32
+ import { useAddEdit } from "@jskit-ai/users-web/client/composables/useAddEdit";
33
+ import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
34
+
35
+ const form = reactive({
36
+ systemPrompt: ""
37
+ });
38
+
39
+ const subtitle = computed(() => {
40
+ if (assistantRuntimeConfig.configScope === "workspace") {
41
+ return `Configure the prompt used on the ${assistantRuntimeConfig.runtimeSurfaceId} surface for this workspace.`;
42
+ }
43
+
44
+ return `Configure the prompt used on the ${assistantRuntimeConfig.runtimeSurfaceId} surface.`;
45
+ });
46
+
47
+ const addEdit = useAddEdit({
48
+ ownershipFilter: assistantRuntimeConfig.settingsSurfaceRequiresWorkspace ? "workspace" : "public",
49
+ surfaceId: assistantRuntimeConfig.settingsSurfaceId,
50
+ access: "auto",
51
+ resource: assistantConfigResource,
52
+ apiSuffix: "/assistant/settings",
53
+ queryKeyFactory: (_surfaceId = "", workspaceSlug = "") =>
54
+ assistantSettingsQueryKey({
55
+ targetSurfaceId: assistantRuntimeConfig.runtimeSurfaceId,
56
+ workspaceSlug
57
+ }),
58
+ viewPermissions: assistantRuntimeConfig.settingsSurfaceRequiresWorkspace
59
+ ? ["workspace.settings.view", "workspace.settings.update"]
60
+ : [],
61
+ savePermissions: assistantRuntimeConfig.settingsSurfaceRequiresWorkspace
62
+ ? ["workspace.settings.update"]
63
+ : [],
64
+ writeMethod: "PATCH",
65
+ placementSource: "assistant.settings-view",
66
+ fallbackLoadError: "Unable to load assistant settings.",
67
+ fallbackSaveError: "Unable to update assistant settings.",
68
+ fieldErrorKeys: ["systemPrompt"],
69
+ model: form,
70
+ parseInput: (rawPayload) =>
71
+ validateOperationSection({
72
+ operation: assistantConfigResource.operations.patch,
73
+ section: "bodyValidator",
74
+ value: rawPayload
75
+ }),
76
+ mapLoadedToModel(model, payload = {}) {
77
+ const settings = payload?.settings && typeof payload.settings === "object" ? payload.settings : {};
78
+ model.systemPrompt = String(settings.systemPrompt || "");
79
+ },
80
+ buildRawPayload(model) {
81
+ return {
82
+ systemPrompt: model.systemPrompt
83
+ };
84
+ }
85
+ });
86
+
87
+ const showFormSkeleton = computed(() => Boolean(addEdit.isInitialLoading));
88
+ </script>
@@ -0,0 +1,10 @@
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 runtime = useAssistantRuntime();
10
+ </script>