@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
@@ -1,33 +1,21 @@
1
1
  import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
2
2
  import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
- import { createRepository as createConversationsRepository } from "./repositories/conversationsRepository.js";
4
- import { createRepository as createMessagesRepository } from "./repositories/messagesRepository.js";
5
- import { createRepository as createAssistantSettingsRepository } from "./repositories/assistantSettingsRepository.js";
6
- import { createChatService } from "./services/chatService.js";
7
- import {
8
- createService as createAssistantSettingsService,
9
- serviceEvents as assistantSettingsServiceEvents
10
- } from "./services/assistantSettingsService.js";
11
3
  import {
12
- createTranscriptService,
13
- serviceEvents as transcriptServiceEvents
14
- } from "./services/transcriptService.js";
15
- import { createAiClient } from "./lib/aiClient.js";
16
- import { createServiceToolCatalog } from "./lib/serviceToolCatalog.js";
17
- import { normalizeOptionalHttpUrl } from "./lib/providers/common.js";
4
+ DEFAULT_AI_TIMEOUT_MS,
5
+ createAiClient,
6
+ createServiceToolCatalog,
7
+ normalizeOptionalHttpUrl,
8
+ normalizeTimeoutMs
9
+ } from "@jskit-ai/assistant-core/server";
10
+ import { assistantRuntimeConfig } from "../shared/assistantRuntimeConfig.js";
18
11
  import { assistantActions } from "./actions.js";
19
12
  import { registerRoutes } from "./registerRoutes.js";
20
-
21
- const DEFAULT_AI_TIMEOUT_MS = 120_000;
22
-
23
- function normalizeInteger(value, fallback) {
24
- const parsed = Number(value);
25
- if (!Number.isInteger(parsed) || parsed < 1) {
26
- return fallback;
27
- }
28
-
29
- return parsed;
30
- }
13
+ import { createRepository as createAssistantConfigRepository } from "./repositories/assistantConfigRepository.js";
14
+ import { createRepository as createConversationsRepository } from "./repositories/conversationsRepository.js";
15
+ import { createRepository as createMessagesRepository } from "./repositories/messagesRepository.js";
16
+ import { createService as createAssistantConfigService } from "./services/assistantConfigService.js";
17
+ import { createChatService } from "./services/chatService.js";
18
+ import { createTranscriptService } from "./services/transcriptService.js";
31
19
 
32
20
  function normalizeStringArray(value) {
33
21
  const source = Array.isArray(value) ? value : [value];
@@ -45,7 +33,7 @@ function resolveAssistantConfig(scope) {
45
33
  context: "assistant AI_BASE_URL"
46
34
  });
47
35
  const model = normalizeText(assistantConfig.model);
48
- const timeoutMs = normalizeInteger(
36
+ const timeoutMs = normalizeTimeoutMs(
49
37
  env.AI_TIMEOUT_MS || assistantConfig.timeoutMs,
50
38
  DEFAULT_AI_TIMEOUT_MS
51
39
  );
@@ -59,16 +47,15 @@ function resolveAssistantConfig(scope) {
59
47
  model,
60
48
  timeoutMs
61
49
  }),
62
- timeoutMs,
63
50
  barredActionIds: normalizeStringArray(assistantConfig.barredActionIds),
64
51
  toolSkipActionPrefixes: normalizeStringArray(assistantConfig.toolSkipActionPrefixes)
65
52
  });
66
53
  }
67
54
 
68
- class AssistantServiceProvider {
55
+ class AssistantProvider {
69
56
  static id = "assistant.chat.service";
70
57
 
71
- static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "users.core", "runtime.realtime"];
58
+ static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "users.core"];
72
59
 
73
60
  register(app) {
74
61
  if (
@@ -77,11 +64,16 @@ class AssistantServiceProvider {
77
64
  typeof app.service !== "function" ||
78
65
  typeof app.actions !== "function"
79
66
  ) {
80
- throw new Error("AssistantServiceProvider requires application singleton()/service()/actions().");
67
+ throw new Error("AssistantProvider requires application singleton()/service()/actions().");
81
68
  }
82
69
 
83
70
  const config = resolveAssistantConfig(app);
84
71
 
72
+ app.singleton("assistant.config.repository", (scope) => {
73
+ const knex = scope.make("jskit.database.knex");
74
+ return createAssistantConfigRepository(knex);
75
+ });
76
+
85
77
  app.singleton("assistant.conversation.repository", (scope) => {
86
78
  const knex = scope.make("jskit.database.knex");
87
79
  return createConversationsRepository(knex);
@@ -92,46 +84,33 @@ class AssistantServiceProvider {
92
84
  return createMessagesRepository(knex);
93
85
  });
94
86
 
95
- app.singleton("assistant.settings.repository", (scope) => {
96
- const knex = scope.make("jskit.database.knex");
97
- return createAssistantSettingsRepository(knex);
98
- });
99
-
100
87
  app.singleton("assistant.ai.client", () => {
101
88
  return createAiClient(config.ai);
102
89
  });
103
90
 
104
91
  app.singleton("assistant.service.tool-catalog", (scope) => {
105
- const skipPrefixes = ["assistant.", ...config.toolSkipActionPrefixes];
106
-
107
92
  return createServiceToolCatalog(scope, {
108
93
  barredActionIds: config.barredActionIds,
109
- skipActionPrefixes: skipPrefixes
94
+ skipActionPrefixes: ["assistant.", ...config.toolSkipActionPrefixes]
110
95
  });
111
96
  });
112
97
 
113
98
  app.service(
114
- "assistant.transcript.service",
99
+ "assistant.config.service",
115
100
  (scope) =>
116
- createTranscriptService({
117
- conversationsRepository: scope.make("assistant.conversation.repository"),
118
- messagesRepository: scope.make("assistant.message.repository")
119
- }),
120
- {
121
- events: transcriptServiceEvents
122
- }
101
+ createAssistantConfigService({
102
+ assistantConfigRepository: scope.make("assistant.config.repository"),
103
+ consoleService: scope.has("consoleService") ? scope.make("consoleService") : null
104
+ })
123
105
  );
124
106
 
125
107
  app.service(
126
- "assistant.settings.service",
108
+ "assistant.transcript.service",
127
109
  (scope) =>
128
- createAssistantSettingsService({
129
- assistantSettingsRepository: scope.make("assistant.settings.repository"),
130
- consoleService: scope.make("consoleService")
131
- }),
132
- {
133
- events: assistantSettingsServiceEvents
134
- }
110
+ createTranscriptService({
111
+ conversationsRepository: scope.make("assistant.conversation.repository"),
112
+ messagesRepository: scope.make("assistant.message.repository")
113
+ })
135
114
  );
136
115
 
137
116
  app.service(
@@ -141,11 +120,8 @@ class AssistantServiceProvider {
141
120
  aiClient: scope.make("assistant.ai.client"),
142
121
  transcriptService: scope.make("assistant.transcript.service"),
143
122
  serviceToolCatalog: scope.make("assistant.service.tool-catalog"),
144
- assistantSettingsService: scope.make("assistant.settings.service")
145
- }),
146
- {
147
- events: {}
148
- }
123
+ assistantConfigService: scope.make("assistant.config.service")
124
+ })
149
125
  );
150
126
 
151
127
  app.actions(
@@ -153,7 +129,7 @@ class AssistantServiceProvider {
153
129
  domain: "assistant",
154
130
  dependencies: {
155
131
  chatService: "assistant.chat.service",
156
- assistantSettingsService: "assistant.settings.service"
132
+ assistantConfigService: "assistant.config.service"
157
133
  }
158
134
  })
159
135
  );
@@ -164,4 +140,4 @@ class AssistantServiceProvider {
164
140
  }
165
141
  }
166
142
 
167
- export { AssistantServiceProvider };
143
+ export { AssistantProvider };
@@ -0,0 +1,9 @@
1
+ const actionIds = Object.freeze({
2
+ chatStream: "assistant.chat.stream",
3
+ conversationsList: "assistant.conversations.list",
4
+ conversationMessagesList: "assistant.conversation.messages.list",
5
+ settingsRead: "assistant.settings.read",
6
+ settingsUpdate: "assistant.settings.update"
7
+ });
8
+
9
+ export { actionIds };
@@ -0,0 +1,190 @@
1
+ import {
2
+ EMPTY_INPUT_VALIDATOR
3
+ } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
4
+ import { workspaceSlugParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
5
+ import {
6
+ assistantConfigResource,
7
+ assistantResource
8
+ } from "@jskit-ai/assistant-core/shared";
9
+ import { assistantRuntimeConfig } from "../shared/assistantRuntimeConfig.js";
10
+ import { actionIds } from "./actionIds.js";
11
+
12
+ const runtimeSurfaces = Object.freeze([assistantRuntimeConfig.runtimeSurfaceId]);
13
+ const settingsSurfaces = Object.freeze([assistantRuntimeConfig.settingsSurfaceId]);
14
+ const runtimeVisibility = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace ? "workspace" : "public";
15
+ const settingsVisibility = assistantRuntimeConfig.settingsSurfaceRequiresWorkspace ? "workspace" : "public";
16
+
17
+ const runtimeQueryInputValidator = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
18
+ ? [workspaceSlugParamsValidator, { query: assistantResource.operations.conversationsList.queryValidator }]
19
+ : { query: assistantResource.operations.conversationsList.queryValidator };
20
+
21
+ const runtimeMessagesInputValidator = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
22
+ ? [
23
+ workspaceSlugParamsValidator,
24
+ assistantResource.operations.conversationMessagesList.paramsValidator,
25
+ {
26
+ query: assistantResource.operations.conversationMessagesList.queryValidator
27
+ }
28
+ ]
29
+ : [
30
+ assistantResource.operations.conversationMessagesList.paramsValidator,
31
+ {
32
+ query: assistantResource.operations.conversationMessagesList.queryValidator
33
+ }
34
+ ];
35
+
36
+ const runtimeChatInputValidator = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
37
+ ? [workspaceSlugParamsValidator, assistantResource.operations.chatStream.bodyValidator]
38
+ : assistantResource.operations.chatStream.bodyValidator;
39
+
40
+ const settingsReadInputValidator = assistantRuntimeConfig.settingsSurfaceRequiresWorkspace
41
+ ? workspaceSlugParamsValidator
42
+ : EMPTY_INPUT_VALIDATOR;
43
+
44
+ const settingsUpdateInputValidator = assistantRuntimeConfig.settingsSurfaceRequiresWorkspace
45
+ ? [
46
+ workspaceSlugParamsValidator,
47
+ {
48
+ patch: assistantConfigResource.operations.patch.bodyValidator
49
+ }
50
+ ]
51
+ : {
52
+ patch: assistantConfigResource.operations.patch.bodyValidator
53
+ };
54
+
55
+ const settingsReadPermission = assistantRuntimeConfig.settingsSurfaceRequiresWorkspace
56
+ ? {
57
+ require: "any",
58
+ permissions: ["workspace.settings.view", "workspace.settings.update"]
59
+ }
60
+ : {
61
+ require: "authenticated"
62
+ };
63
+
64
+ const settingsUpdatePermission = assistantRuntimeConfig.settingsSurfaceRequiresWorkspace
65
+ ? {
66
+ require: "all",
67
+ permissions: ["workspace.settings.update"]
68
+ }
69
+ : {
70
+ require: "authenticated"
71
+ };
72
+
73
+ const assistantActions = Object.freeze([
74
+ {
75
+ id: actionIds.chatStream,
76
+ version: 1,
77
+ kind: "stream",
78
+ channels: ["api", "internal"],
79
+ surfaces: runtimeSurfaces,
80
+ permission: {
81
+ require: "authenticated"
82
+ },
83
+ inputValidator: runtimeChatInputValidator,
84
+ visibility: runtimeVisibility,
85
+ idempotency: "optional",
86
+ audit: {
87
+ actionName: actionIds.chatStream
88
+ },
89
+ observability: {},
90
+ async execute(input, context, deps) {
91
+ return deps.chatService.streamChat(input, {
92
+ context,
93
+ streamWriter: deps.streamWriter,
94
+ abortSignal: deps.abortSignal
95
+ });
96
+ }
97
+ },
98
+ {
99
+ id: actionIds.conversationsList,
100
+ version: 1,
101
+ kind: "query",
102
+ channels: ["api", "internal"],
103
+ surfaces: runtimeSurfaces,
104
+ permission: {
105
+ require: "authenticated"
106
+ },
107
+ inputValidator: runtimeQueryInputValidator,
108
+ outputValidator: assistantResource.operations.conversationsList.outputValidator,
109
+ visibility: runtimeVisibility,
110
+ idempotency: "none",
111
+ audit: {
112
+ actionName: actionIds.conversationsList
113
+ },
114
+ observability: {},
115
+ async execute(input, context, deps) {
116
+ return deps.chatService.listConversations(input.query, {
117
+ context,
118
+ input
119
+ });
120
+ }
121
+ },
122
+ {
123
+ id: actionIds.conversationMessagesList,
124
+ version: 1,
125
+ kind: "query",
126
+ channels: ["api", "internal"],
127
+ surfaces: runtimeSurfaces,
128
+ permission: {
129
+ require: "authenticated"
130
+ },
131
+ inputValidator: runtimeMessagesInputValidator,
132
+ outputValidator: assistantResource.operations.conversationMessagesList.outputValidator,
133
+ visibility: runtimeVisibility,
134
+ idempotency: "none",
135
+ audit: {
136
+ actionName: actionIds.conversationMessagesList
137
+ },
138
+ observability: {},
139
+ async execute(input, context, deps) {
140
+ return deps.chatService.getConversationMessages(input.conversationId, input.query, {
141
+ context,
142
+ input
143
+ });
144
+ }
145
+ },
146
+ {
147
+ id: actionIds.settingsRead,
148
+ version: 1,
149
+ kind: "query",
150
+ channels: ["api", "automation", "internal"],
151
+ surfaces: settingsSurfaces,
152
+ permission: settingsReadPermission,
153
+ inputValidator: settingsReadInputValidator,
154
+ outputValidator: assistantConfigResource.operations.view.outputValidator,
155
+ visibility: settingsVisibility,
156
+ idempotency: "none",
157
+ audit: {
158
+ actionName: actionIds.settingsRead
159
+ },
160
+ observability: {},
161
+ async execute(input, context, deps) {
162
+ return deps.assistantConfigService.getSettings(input, {
163
+ context
164
+ });
165
+ }
166
+ },
167
+ {
168
+ id: actionIds.settingsUpdate,
169
+ version: 1,
170
+ kind: "command",
171
+ channels: ["api", "automation", "internal"],
172
+ surfaces: settingsSurfaces,
173
+ permission: settingsUpdatePermission,
174
+ inputValidator: settingsUpdateInputValidator,
175
+ outputValidator: assistantConfigResource.operations.patch.outputValidator,
176
+ visibility: settingsVisibility,
177
+ idempotency: "optional",
178
+ audit: {
179
+ actionName: actionIds.settingsUpdate
180
+ },
181
+ observability: {},
182
+ async execute(input, context, deps) {
183
+ return deps.assistantConfigService.updateSettings(input, input.patch, {
184
+ context
185
+ });
186
+ }
187
+ }
188
+ ]);
189
+
190
+ export { assistantActions };
@@ -0,0 +1,296 @@
1
+ import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
2
+ import { buildWorkspaceInputFromRouteParams } from "@jskit-ai/users-core/server/support/workspaceRouteInput";
3
+ import { workspaceSlugParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
4
+ import {
5
+ assistantConfigResource,
6
+ assistantResource,
7
+ resolveAssistantApiBasePath,
8
+ resolveAssistantSettingsApiPath
9
+ } from "@jskit-ai/assistant-core/shared";
10
+ import {
11
+ endNdjson,
12
+ mapStreamError,
13
+ setNdjsonHeaders,
14
+ writeNdjson
15
+ } from "@jskit-ai/assistant-core/server";
16
+ import { assistantRuntimeConfig } from "../shared/assistantRuntimeConfig.js";
17
+ import { actionIds } from "./actionIds.js";
18
+
19
+ const runtimeVisibility = assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace ? "workspace" : "public";
20
+ const settingsVisibility = assistantRuntimeConfig.settingsSurfaceRequiresWorkspace ? "workspace" : "public";
21
+ const runtimeRouteBase = resolveAssistantApiBasePath({
22
+ requiresWorkspace: assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
23
+ });
24
+ const settingsRouteBase = resolveAssistantSettingsApiPath({
25
+ requiresWorkspace: assistantRuntimeConfig.settingsSurfaceRequiresWorkspace
26
+ });
27
+
28
+ function buildWorkspaceRouteConfig(requiresWorkspace, baseConfig = {}) {
29
+ if (requiresWorkspace !== true) {
30
+ return baseConfig;
31
+ }
32
+
33
+ return {
34
+ ...baseConfig,
35
+ paramsValidator: workspaceSlugParamsValidator
36
+ };
37
+ }
38
+
39
+ function readWorkspaceInput(request, requiresWorkspace) {
40
+ if (requiresWorkspace !== true) {
41
+ return {};
42
+ }
43
+
44
+ return buildWorkspaceInputFromRouteParams(request?.input?.params);
45
+ }
46
+
47
+ function registerRoutes(app) {
48
+ if (!app || typeof app.make !== "function") {
49
+ throw new Error("registerRoutes requires application make().");
50
+ }
51
+
52
+ const router = app.make("jskit.http.router");
53
+
54
+ router.register(
55
+ "GET",
56
+ settingsRouteBase,
57
+ buildWorkspaceRouteConfig(assistantRuntimeConfig.settingsSurfaceRequiresWorkspace, {
58
+ auth: "required",
59
+ surface: assistantRuntimeConfig.settingsSurfaceId,
60
+ visibility: settingsVisibility,
61
+ meta: {
62
+ tags: ["assistant", "settings"],
63
+ summary: "Get assistant settings."
64
+ },
65
+ responseValidators: withStandardErrorResponses({
66
+ 200: assistantConfigResource.operations.view.outputValidator
67
+ })
68
+ }),
69
+ async function assistantSettingsReadRoute(request, reply) {
70
+ const response = await request.executeAction({
71
+ actionId: actionIds.settingsRead,
72
+ input: {
73
+ ...readWorkspaceInput(request, assistantRuntimeConfig.settingsSurfaceRequiresWorkspace)
74
+ }
75
+ });
76
+
77
+ reply.code(200).send(response);
78
+ }
79
+ );
80
+
81
+ router.register(
82
+ "PATCH",
83
+ settingsRouteBase,
84
+ buildWorkspaceRouteConfig(assistantRuntimeConfig.settingsSurfaceRequiresWorkspace, {
85
+ auth: "required",
86
+ surface: assistantRuntimeConfig.settingsSurfaceId,
87
+ visibility: settingsVisibility,
88
+ meta: {
89
+ tags: ["assistant", "settings"],
90
+ summary: "Update assistant settings."
91
+ },
92
+ bodyValidator: assistantConfigResource.operations.patch.bodyValidator,
93
+ responseValidators: withStandardErrorResponses(
94
+ {
95
+ 200: assistantConfigResource.operations.patch.outputValidator
96
+ },
97
+ {
98
+ includeValidation400: true
99
+ }
100
+ )
101
+ }),
102
+ async function assistantSettingsPatchRoute(request, reply) {
103
+ const response = await request.executeAction({
104
+ actionId: actionIds.settingsUpdate,
105
+ input: {
106
+ ...readWorkspaceInput(request, assistantRuntimeConfig.settingsSurfaceRequiresWorkspace),
107
+ patch: request.input.body
108
+ }
109
+ });
110
+
111
+ reply.code(200).send(response);
112
+ }
113
+ );
114
+
115
+ router.register(
116
+ "POST",
117
+ `${runtimeRouteBase}/chat/stream`,
118
+ buildWorkspaceRouteConfig(assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace, {
119
+ auth: "required",
120
+ surface: assistantRuntimeConfig.runtimeSurfaceId,
121
+ visibility: runtimeVisibility,
122
+ meta: {
123
+ tags: ["assistant"],
124
+ summary: "Stream assistant response."
125
+ },
126
+ bodyValidator: assistantResource.operations.chatStream.bodyValidator
127
+ }),
128
+ async function assistantChatStreamRoute(request, reply) {
129
+ const abortController = new AbortController();
130
+ const requestBody = request?.input?.body && typeof request.input.body === "object" ? request.input.body : {};
131
+ const closeListener = () => {
132
+ abortController.abort();
133
+ };
134
+
135
+ let streamStarted = false;
136
+
137
+ function ensureStreamStarted() {
138
+ if (streamStarted) {
139
+ return;
140
+ }
141
+
142
+ setNdjsonHeaders(reply);
143
+ reply.code(200);
144
+ reply.hijack();
145
+ if (typeof reply.raw.flushHeaders === "function") {
146
+ reply.raw.flushHeaders();
147
+ }
148
+ streamStarted = true;
149
+ }
150
+
151
+ const streamWriter = Object.freeze({
152
+ sendMeta(payload = {}) {
153
+ ensureStreamStarted();
154
+ writeNdjson(reply, payload);
155
+ },
156
+ sendAssistantDelta(payload = {}) {
157
+ ensureStreamStarted();
158
+ writeNdjson(reply, payload);
159
+ },
160
+ sendAssistantMessage(payload = {}) {
161
+ ensureStreamStarted();
162
+ writeNdjson(reply, payload);
163
+ },
164
+ sendToolCall(payload = {}) {
165
+ ensureStreamStarted();
166
+ writeNdjson(reply, payload);
167
+ },
168
+ sendToolResult(payload = {}) {
169
+ ensureStreamStarted();
170
+ writeNdjson(reply, payload);
171
+ },
172
+ sendError(payload = {}) {
173
+ ensureStreamStarted();
174
+ writeNdjson(reply, payload);
175
+ },
176
+ sendDone(payload = {}) {
177
+ ensureStreamStarted();
178
+ writeNdjson(reply, payload);
179
+ }
180
+ });
181
+
182
+ try {
183
+ request.raw.on("close", closeListener);
184
+
185
+ await request.executeAction({
186
+ actionId: actionIds.chatStream,
187
+ input: {
188
+ ...readWorkspaceInput(request, assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace),
189
+ messageId: requestBody.messageId,
190
+ conversationId: requestBody.conversationId,
191
+ input: requestBody.input,
192
+ history: requestBody.history,
193
+ clientContext: requestBody.clientContext
194
+ },
195
+ deps: {
196
+ streamWriter,
197
+ abortSignal: abortController.signal
198
+ }
199
+ });
200
+
201
+ if (streamStarted) {
202
+ endNdjson(reply);
203
+ return;
204
+ }
205
+
206
+ reply.code(204).send();
207
+ } catch (error) {
208
+ if (!streamStarted) {
209
+ const statusCode = Number(error?.status || error?.statusCode || 500);
210
+ const safeStatusCode = Number.isInteger(statusCode) && statusCode >= 400 && statusCode <= 599 ? statusCode : 500;
211
+ reply.code(safeStatusCode).send({
212
+ error: safeStatusCode >= 500 ? "Internal server error." : String(error?.message || "Request failed.")
213
+ });
214
+ return;
215
+ }
216
+
217
+ const streamError = mapStreamError(error);
218
+ writeNdjson(reply, {
219
+ type: "error",
220
+ ...streamError
221
+ });
222
+ writeNdjson(reply, {
223
+ type: "done",
224
+ status: "failed"
225
+ });
226
+ endNdjson(reply);
227
+ } finally {
228
+ request.raw.off("close", closeListener);
229
+ }
230
+ }
231
+ );
232
+
233
+ router.register(
234
+ "GET",
235
+ `${runtimeRouteBase}/conversations`,
236
+ buildWorkspaceRouteConfig(assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace, {
237
+ auth: "required",
238
+ surface: assistantRuntimeConfig.runtimeSurfaceId,
239
+ visibility: runtimeVisibility,
240
+ meta: {
241
+ tags: ["assistant"],
242
+ summary: "List assistant conversations."
243
+ },
244
+ queryValidator: assistantResource.operations.conversationsList.queryValidator,
245
+ responseValidators: withStandardErrorResponses({
246
+ 200: assistantResource.operations.conversationsList.outputValidator
247
+ })
248
+ }),
249
+ async function assistantConversationsRoute(request, reply) {
250
+ const response = await request.executeAction({
251
+ actionId: actionIds.conversationsList,
252
+ input: {
253
+ ...readWorkspaceInput(request, assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace),
254
+ query: request.input.query
255
+ }
256
+ });
257
+
258
+ reply.code(200).send(response);
259
+ }
260
+ );
261
+
262
+ router.register(
263
+ "GET",
264
+ `${runtimeRouteBase}/conversations/:conversationId/messages`,
265
+ buildWorkspaceRouteConfig(assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace, {
266
+ auth: "required",
267
+ surface: assistantRuntimeConfig.runtimeSurfaceId,
268
+ visibility: runtimeVisibility,
269
+ meta: {
270
+ tags: ["assistant"],
271
+ summary: "List assistant conversation messages."
272
+ },
273
+ paramsValidator: assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace
274
+ ? [workspaceSlugParamsValidator, assistantResource.operations.conversationMessagesList.paramsValidator]
275
+ : assistantResource.operations.conversationMessagesList.paramsValidator,
276
+ queryValidator: assistantResource.operations.conversationMessagesList.queryValidator,
277
+ responseValidators: withStandardErrorResponses({
278
+ 200: assistantResource.operations.conversationMessagesList.outputValidator
279
+ })
280
+ }),
281
+ async function assistantConversationMessagesRoute(request, reply) {
282
+ const response = await request.executeAction({
283
+ actionId: actionIds.conversationMessagesList,
284
+ input: {
285
+ ...readWorkspaceInput(request, assistantRuntimeConfig.runtimeSurfaceRequiresWorkspace),
286
+ conversationId: request.input.params.conversationId,
287
+ query: request.input.query
288
+ }
289
+ });
290
+
291
+ reply.code(200).send(response);
292
+ }
293
+ );
294
+ }
295
+
296
+ export { registerRoutes };