@jskit-ai/assistant-runtime 0.2.9 → 0.2.10
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.
- package/README.md +1 -1
- package/fixtures/canonical-conversation/main.js +2 -1
- package/fixtures/conversation/main.js +2 -1
- package/fixtures/conversation/vite.config.mjs +4 -3
- package/migrations/assistant_config_initial.cjs +1 -5
- package/migrations/assistant_transcripts_initial.cjs +3 -9
- package/migrations/assistant_transcripts_summary.cjs +28 -0
- package/migrations/assistant_turn_requests.cjs +2 -1
- package/migrations/constraints/assistant_users_foreign_keys.cjs +22 -0
- package/migrations/constraints/assistant_workspaces_foreign_keys.cjs +26 -0
- package/package.json +17 -10
- package/patterns/assistant-surface/example/src/ApplicationAiFeature.js +4 -2
- package/src/client/components/AssistantConversationClientElement.vue +10 -4
- package/src/client/components/AssistantSurfaceClientElement.vue +8 -4
- package/src/client/composables/conversationGoal.js +266 -0
- package/src/client/composables/conversationHistory.js +215 -0
- package/src/client/composables/conversationRuntime.js +624 -0
- package/src/client/composables/useAssistantConversation.js +208 -884
- package/src/client/composables/useAssistantRuntime.js +60 -45
- package/src/client/providers/AssistantClientProvider.js +1 -0
- package/src/client/support/assistantRuntimeState.js +6 -34
- package/src/client/support/conversationFailures.js +30 -0
- package/src/client/support/conversationRestoreSupport.js +13 -5
- package/src/client/support/subscribeAssistantConversation.js +32 -9
- package/src/client/support/workspaceScopeSupport.js +13 -22
- package/src/client/types.js +46 -0
- package/src/server/AssistantProvider.js +45 -17
- package/src/server/actions.js +162 -201
- package/src/server/conversationOperations.js +141 -0
- package/src/server/createAssistantRuntime.js +83 -44
- package/src/server/createAssistantSettingsActions.js +69 -47
- package/src/server/hostedAssistant.js +21 -0
- package/src/server/index.js +1 -0
- package/src/server/registerConversationSubscriptions.js +34 -11
- package/src/server/registerFastifyConversations.js +18 -6
- package/src/server/registerRoutes.js +5 -2
- package/src/server/registerSettingsRoutes.js +1 -1
- package/src/server/repositories/assistantConfigRepository.js +10 -20
- package/src/server/repositories/conversationsRepository.js +35 -10
- package/src/server/repositories/messagesRepository.js +14 -10
- package/src/server/repositories/repositoryPersistenceUtils.js +1 -6
- package/src/server/repositories/turnRequestsRepository.js +6 -1
- package/src/server/services/assistantConfigService.js +25 -23
- package/src/server/services/chatService.js +57 -61
- package/src/server/services/transcriptService.js +15 -7
- package/src/server/sqlConversationStorage.js +90 -18
- package/src/server/support/assistantServerConfig.js +1 -1
- package/src/server/support/assistantStreamWriter.js +7 -1
- package/src/server/support/createSurfaceAwareToolCatalog.js +35 -17
- package/src/server/types.js +38 -0
- package/src/shared/assistantSurfaces.js +2 -2
- package/test/assistantConversationOperations.test.js +5 -5
- package/test/assistantRuntimeState.test.js +1 -16
- package/test/chatServiceLifecycle.test.js +62 -154
- package/test/createSurfaceAwareToolCatalog.test.js +5 -0
- package/test/featureRuntime.test.js +12 -6
- package/test/lazyAppConfig.test.js +3 -5
- package/test/packageMetadata.test.js +7 -1
- package/test/sqlConversationStorage.mysql.test.js +89 -0
- package/test/sqlConversationStorage.test.js +161 -16
- package/test/support/memoryAssistantRows.js +6 -1
- package/test/support/scriptedModel.js +32 -0
- package/test/turnRequests.test.js +4 -2
- package/test/usersForeignKeysMigration.test.js +46 -0
- package/test/workspacesForeignKeysMigration.test.js +66 -0
- package/src/client/support/accessDenied.js +0 -6
package/src/server/actions.js
CHANGED
|
@@ -6,13 +6,12 @@ import {
|
|
|
6
6
|
deepFreeze
|
|
7
7
|
} from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
8
8
|
import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
|
|
9
|
-
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
10
9
|
import {
|
|
11
10
|
assistantResource
|
|
12
11
|
} from "@jskit-ai/assistant-core/shared";
|
|
13
12
|
import { actionIds } from "./actionIds.js";
|
|
14
13
|
import { createAssistantSettingsActions } from "./createAssistantSettingsActions.js";
|
|
15
|
-
import {
|
|
14
|
+
import { createConversationOperations } from "./conversationOperations.js";
|
|
16
15
|
import {
|
|
17
16
|
assistantConversationIdInputValidator,
|
|
18
17
|
assistantConversationGoalBodyValidator,
|
|
@@ -29,6 +28,13 @@ import {
|
|
|
29
28
|
resolveAssistantSurfacesConfig
|
|
30
29
|
} from "../shared/assistantSurfaces.js";
|
|
31
30
|
|
|
31
|
+
/** @typedef {import("./types.js").AssistantConfigService} AssistantConfigService */
|
|
32
|
+
/** @typedef {import("./types.js").ChatService} ChatService */
|
|
33
|
+
/** @typedef {import("./types.js").ConversationAccess} ConversationAccess */
|
|
34
|
+
/** @typedef {import("./types.js").ConversationRuntime} ConversationRuntime */
|
|
35
|
+
/** @typedef {import("./types.js").WorkspaceScopeSupport} WorkspaceScopeSupport */
|
|
36
|
+
/** @typedef {ReturnType<typeof createConversationOperations>} ConversationOperations */
|
|
37
|
+
|
|
32
38
|
const runtimeConversationsListQueryInputValidator = deepFreeze({
|
|
33
39
|
schema: createSchema({
|
|
34
40
|
query: {
|
|
@@ -43,7 +49,7 @@ const runtimeConversationsListQueryInputValidator = deepFreeze({
|
|
|
43
49
|
const runtimeConversationsListInputValidator = composeSchemaDefinitions(
|
|
44
50
|
[assistantTargetSurfaceInputValidator, runtimeConversationsListQueryInputValidator],
|
|
45
51
|
{
|
|
46
|
-
mode: "
|
|
52
|
+
mode: "create",
|
|
47
53
|
context: "assistant-runtime conversations list action input"
|
|
48
54
|
}
|
|
49
55
|
);
|
|
@@ -66,7 +72,7 @@ const runtimeConversationMessagesListInputValidator = composeSchemaDefinitions(
|
|
|
66
72
|
runtimeConversationMessagesListQueryInputValidator
|
|
67
73
|
],
|
|
68
74
|
{
|
|
69
|
-
mode: "
|
|
75
|
+
mode: "create",
|
|
70
76
|
context: "assistant-runtime conversation messages action input"
|
|
71
77
|
}
|
|
72
78
|
);
|
|
@@ -77,7 +83,7 @@ const runtimeChatStreamInputValidator = composeSchemaDefinitions(
|
|
|
77
83
|
assistantResource.operations.chatStream.body
|
|
78
84
|
],
|
|
79
85
|
{
|
|
80
|
-
mode: "
|
|
86
|
+
mode: "create",
|
|
81
87
|
context: "assistant-runtime chat stream action input"
|
|
82
88
|
}
|
|
83
89
|
);
|
|
@@ -90,41 +96,63 @@ function configuredAssistantSurfaces(config = {}) {
|
|
|
90
96
|
|
|
91
97
|
const conversationInputValidator = composeSchemaDefinitions(
|
|
92
98
|
[assistantTargetSurfaceInputValidator, assistantConversationIdInputValidator],
|
|
93
|
-
{ mode: "
|
|
99
|
+
{ mode: "create", context: "assistant-runtime conversation action input" }
|
|
94
100
|
);
|
|
95
101
|
|
|
96
102
|
const conversationDeliveryInputValidator = composeSchemaDefinitions(
|
|
97
103
|
[conversationInputValidator, assistantConversationMessageIdInputValidator],
|
|
98
|
-
{ mode: "
|
|
104
|
+
{ mode: "create", context: "assistant-runtime conversation delivery inspection input" }
|
|
99
105
|
);
|
|
100
106
|
|
|
101
107
|
const conversationReadInputValidator = composeSchemaDefinitions(
|
|
102
108
|
[conversationInputValidator, assistantConversationReadQueryValidator],
|
|
103
|
-
{ mode: "
|
|
109
|
+
{ mode: "create", context: "assistant-runtime conversation read action input" }
|
|
104
110
|
);
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
/**
|
|
113
|
+
* The JSON:API document of a surface's conversations.
|
|
114
|
+
* @param {ChatService} chatService
|
|
115
|
+
* @param {object} input
|
|
116
|
+
* @param {object} context
|
|
117
|
+
*/
|
|
118
|
+
async function listConversationsDocument(chatService, input, context) {
|
|
119
|
+
return returnJsonApiData(await chatService.listConversations(input.query, {
|
|
120
|
+
context,
|
|
121
|
+
input
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The JSON:API document of one conversation's messages.
|
|
127
|
+
* @param {ChatService} chatService
|
|
128
|
+
* @param {object} input
|
|
129
|
+
* @param {object} context
|
|
130
|
+
*/
|
|
131
|
+
async function listConversationMessagesDocument(chatService, input, context) {
|
|
132
|
+
return returnJsonApiData(await chatService.getConversationMessages(input.conversationId, input.query, {
|
|
133
|
+
context,
|
|
134
|
+
input
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {object} dependencies
|
|
140
|
+
* @param {ConversationOperations} dependencies.conversationOperations
|
|
141
|
+
* @param {object | null} dependencies.conversationDataSchema
|
|
142
|
+
* @param {object | null} dependencies.conversationConfigurationSchema
|
|
143
|
+
* @param {object | null} dependencies.conversationSelectionSchema
|
|
144
|
+
* @param {object | null} dependencies.conversationReplacementSchema
|
|
145
|
+
* @param {string[]} dependencies.runtimeSurfaces
|
|
146
|
+
*/
|
|
147
|
+
function createConversationActions({ conversationOperations, conversationDataSchema, conversationConfigurationSchema,
|
|
148
|
+
conversationSelectionSchema, conversationReplacementSchema, runtimeSurfaces }) {
|
|
108
149
|
const conversationSendInputValidator = composeSchemaDefinitions(
|
|
109
150
|
[conversationInputValidator, conversationSendBodyValidator(conversationDataSchema)],
|
|
110
|
-
{ mode: "
|
|
151
|
+
{ mode: "create", context: "assistant-runtime conversation send action input" }
|
|
111
152
|
);
|
|
112
|
-
async function openConversation(input, context) {
|
|
113
|
-
const assistantSurface = requireAssistantSurface(config, input.targetSurfaceId);
|
|
114
|
-
let conversationContext = context;
|
|
115
|
-
if (assistantSurface.runtimeSurfaceRequiresWorkspace) {
|
|
116
|
-
if (typeof workspaceScopeSupport?.resolveWorkspace !== "function") {
|
|
117
|
-
throw new Error("Assistant conversation actions require workspace server scope support.");
|
|
118
|
-
}
|
|
119
|
-
const workspace = workspaceScopeSupport.resolveWorkspace(context, { workspaceSlug: input.workspaceSlug });
|
|
120
|
-
if (!workspace?.id) throw new AppError(409, "Workspace selection required.", { code: "assistant.runtime.workspace.selectionRequired" });
|
|
121
|
-
conversationContext = { ...context, workspace };
|
|
122
|
-
}
|
|
123
|
-
return conversationRuntime.open({ id: input.conversationId, context: conversationContext });
|
|
124
|
-
}
|
|
125
153
|
|
|
126
|
-
return [
|
|
127
|
-
{
|
|
154
|
+
return Object.freeze([
|
|
155
|
+
Object.freeze({
|
|
128
156
|
id: actionIds.conversationRead,
|
|
129
157
|
version: 1,
|
|
130
158
|
kind: "query",
|
|
@@ -133,17 +161,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
133
161
|
permission: { require: "authenticated" },
|
|
134
162
|
input: conversationReadInputValidator,
|
|
135
163
|
output: null,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const options = {};
|
|
140
|
-
for (const key of ["beforeTurnId", "limit"]) {
|
|
141
|
-
if (Object.hasOwn(input, key)) options[key] = input[key];
|
|
142
|
-
}
|
|
143
|
-
return Object.keys(options).length ? conversation.read(options) : conversation.read();
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
{
|
|
164
|
+
execute: (input, context) => conversationOperations.read(input, context)
|
|
165
|
+
}),
|
|
166
|
+
Object.freeze({
|
|
147
167
|
id: actionIds.conversationSend,
|
|
148
168
|
version: 1,
|
|
149
169
|
kind: "command",
|
|
@@ -152,19 +172,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
152
172
|
permission: { require: "authenticated" },
|
|
153
173
|
input: conversationSendInputValidator,
|
|
154
174
|
output: null,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return conversation.send({
|
|
159
|
-
messageId: input.messageId,
|
|
160
|
-
text: input.text,
|
|
161
|
-
...(conversationDataSchema && Object.hasOwn(input, "data") ? { data: input.data } : {}),
|
|
162
|
-
...(Object.hasOwn(input, "attachmentIds") ? { attachmentIds: input.attachmentIds } : {}),
|
|
163
|
-
...(Object.hasOwn(input, "steer") ? { steer: input.steer } : {})
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
{
|
|
175
|
+
execute: (input, context) => conversationOperations.send(input, context)
|
|
176
|
+
}),
|
|
177
|
+
Object.freeze({
|
|
168
178
|
id: actionIds.conversationCancel,
|
|
169
179
|
version: 1,
|
|
170
180
|
kind: "command",
|
|
@@ -173,13 +183,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
173
183
|
permission: { require: "authenticated" },
|
|
174
184
|
input: conversationInputValidator,
|
|
175
185
|
output: null,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return conversation.cancel();
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
{
|
|
186
|
+
execute: (input, context) => conversationOperations.cancel(input, context)
|
|
187
|
+
}),
|
|
188
|
+
Object.freeze({
|
|
183
189
|
id: actionIds.conversationSubscribe,
|
|
184
190
|
version: 1,
|
|
185
191
|
kind: "query",
|
|
@@ -188,29 +194,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
188
194
|
permission: { require: "authenticated" },
|
|
189
195
|
input: conversationInputValidator,
|
|
190
196
|
output: null,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
throw new TypeError("Conversation subscriptions require their server-owned event and cleanup callbacks.");
|
|
195
|
-
}
|
|
196
|
-
if (deps.isCurrent?.() === false) return null;
|
|
197
|
-
deps.onStage?.("conversation-open");
|
|
198
|
-
const conversation = await openConversation(input, context);
|
|
199
|
-
if (deps.isCurrent?.() === false) return null;
|
|
200
|
-
deps.onStage?.("observer-attach");
|
|
201
|
-
const release = await conversation.subscribe(deps.onEvent);
|
|
202
|
-
try {
|
|
203
|
-
deps.onRelease(release);
|
|
204
|
-
if (deps.isCurrent?.() === false) return null;
|
|
205
|
-
deps.onStage?.("snapshot-read");
|
|
206
|
-
return await conversation.read();
|
|
207
|
-
} catch (error) {
|
|
208
|
-
release();
|
|
209
|
-
throw error;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
{
|
|
197
|
+
execute: (input, context, deps) => conversationOperations.subscribe(input, context, deps)
|
|
198
|
+
}),
|
|
199
|
+
Object.freeze({
|
|
214
200
|
id: actionIds.conversationInspectDelivery,
|
|
215
201
|
version: 1,
|
|
216
202
|
kind: "command",
|
|
@@ -219,13 +205,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
219
205
|
permission: { require: "authenticated" },
|
|
220
206
|
input: conversationDeliveryInputValidator,
|
|
221
207
|
output: null,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return conversation.inspectDelivery({ messageId: input.messageId });
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
{
|
|
208
|
+
execute: (input, context) => conversationOperations.inspectDelivery(input, context)
|
|
209
|
+
}),
|
|
210
|
+
Object.freeze({
|
|
229
211
|
id: actionIds.conversationCancelDelivery,
|
|
230
212
|
version: 1,
|
|
231
213
|
kind: "command",
|
|
@@ -234,13 +216,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
234
216
|
permission: { require: "authenticated" },
|
|
235
217
|
input: conversationDeliveryInputValidator,
|
|
236
218
|
output: null,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return conversation.cancelDelivery({ messageId: input.messageId });
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
{
|
|
219
|
+
execute: (input, context) => conversationOperations.cancelDelivery(input, context)
|
|
220
|
+
}),
|
|
221
|
+
Object.freeze({
|
|
244
222
|
id: actionIds.conversationGoalRead,
|
|
245
223
|
version: 1,
|
|
246
224
|
kind: "query",
|
|
@@ -249,12 +227,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
249
227
|
permission: { require: "authenticated" },
|
|
250
228
|
input: conversationInputValidator,
|
|
251
229
|
output: null,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
{
|
|
230
|
+
execute: (input, context) => conversationOperations.readGoal(input, context)
|
|
231
|
+
}),
|
|
232
|
+
Object.freeze({
|
|
258
233
|
id: actionIds.conversationGoalUpdate,
|
|
259
234
|
version: 1,
|
|
260
235
|
kind: "command",
|
|
@@ -265,17 +240,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
265
240
|
mode: "replace", context: "assistant-runtime conversation goal action input"
|
|
266
241
|
}),
|
|
267
242
|
output: null,
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const goal = { action: input.action, expectedSegmentId: input.expectedSegmentId };
|
|
272
|
-
for (const key of ["expectedGoalId", "messageId", "objective", "tokenBudget", "attachmentIds"]) {
|
|
273
|
-
if (Object.hasOwn(input, key)) goal[key] = input[key];
|
|
274
|
-
}
|
|
275
|
-
return conversation.updateGoal(goal);
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
...(conversationConfigurationSchema ? [{
|
|
243
|
+
execute: (input, context) => conversationOperations.updateGoal(input, context)
|
|
244
|
+
}),
|
|
245
|
+
...(conversationConfigurationSchema ? Object.freeze([Object.freeze({
|
|
279
246
|
id: actionIds.conversationConfigure,
|
|
280
247
|
version: 1,
|
|
281
248
|
kind: "command",
|
|
@@ -286,12 +253,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
286
253
|
mode: "replace", context: "assistant-runtime declared conversation configuration"
|
|
287
254
|
}),
|
|
288
255
|
output: null,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
}] : []),
|
|
294
|
-
...(conversationSelectionSchema ? [{
|
|
256
|
+
execute: (input, context) => conversationOperations.configure(input, context)
|
|
257
|
+
})]) : []),
|
|
258
|
+
...(conversationSelectionSchema ? Object.freeze([Object.freeze({
|
|
295
259
|
id: actionIds.conversationSelect,
|
|
296
260
|
version: 1,
|
|
297
261
|
kind: "command",
|
|
@@ -302,12 +266,9 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
302
266
|
mode: "replace", context: "assistant-runtime declared conversation selection"
|
|
303
267
|
}),
|
|
304
268
|
output: null,
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
}] : []),
|
|
310
|
-
...(conversationReplacementSchema ? [{
|
|
269
|
+
execute: (input, context) => conversationOperations.select(input, context)
|
|
270
|
+
})]) : []),
|
|
271
|
+
...(conversationReplacementSchema ? Object.freeze([Object.freeze({
|
|
311
272
|
id: actionIds.conversationReplace,
|
|
312
273
|
version: 1,
|
|
313
274
|
kind: "command",
|
|
@@ -318,41 +279,56 @@ function createConversationActions({ conversationRuntime, conversationDataSchema
|
|
|
318
279
|
mode: "replace", context: "assistant-runtime declared conversation replacement"
|
|
319
280
|
}),
|
|
320
281
|
output: null,
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
}] : [])
|
|
326
|
-
];
|
|
282
|
+
execute: (input, context) => conversationOperations.replace(input, context)
|
|
283
|
+
})]) : [])
|
|
284
|
+
]);
|
|
327
285
|
}
|
|
328
286
|
|
|
287
|
+
/**
|
|
288
|
+
* The assistant's actions: over a conversation runtime the application supplies, its
|
|
289
|
+
* conversation actions (wrapped by the host's access owner when it supplies one) and
|
|
290
|
+
* the settings actions when it supplies a settings service; otherwise the chat stream,
|
|
291
|
+
* transcript and settings actions of the package's own tables. A configuration
|
|
292
|
+
* without a runtime and a settings surface has none.
|
|
293
|
+
* @param {object} dependencies
|
|
294
|
+
* @param {AssistantConfigService | null} dependencies.assistantConfigService
|
|
295
|
+
* @param {ChatService} [dependencies.chatService] The chat service of the package's own tables, without a conversation runtime.
|
|
296
|
+
* @param {ConversationRuntime | null} dependencies.conversationRuntime
|
|
297
|
+
* @param {ConversationAccess | null} dependencies.conversationAccess
|
|
298
|
+
* @param {object | null} dependencies.conversationDataSchema
|
|
299
|
+
* @param {object | null} dependencies.conversationConfigurationSchema
|
|
300
|
+
* @param {object | null} dependencies.conversationSelectionSchema
|
|
301
|
+
* @param {object | null} dependencies.conversationReplacementSchema
|
|
302
|
+
* @param {WorkspaceScopeSupport | null} dependencies.workspaceScopeSupport
|
|
303
|
+
* @param {Record<string, any>} dependencies.config
|
|
304
|
+
*/
|
|
329
305
|
function createAssistantActions({
|
|
330
306
|
assistantConfigService,
|
|
331
307
|
chatService,
|
|
332
|
-
conversationRuntime
|
|
333
|
-
conversationAccess
|
|
334
|
-
conversationDataSchema
|
|
335
|
-
conversationConfigurationSchema
|
|
336
|
-
conversationSelectionSchema
|
|
337
|
-
conversationReplacementSchema
|
|
338
|
-
workspaceScopeSupport
|
|
339
|
-
config
|
|
340
|
-
}
|
|
341
|
-
if (conversationRuntime && typeof conversationRuntime.open !== "function") {
|
|
342
|
-
throw new TypeError("Assistant conversation actions require conversationRuntime.open().");
|
|
343
|
-
}
|
|
344
|
-
if (!conversationRuntime && (!assistantConfigService || !chatService)) {
|
|
345
|
-
throw new TypeError("createAssistantActions requires assistantConfigService and chatService.");
|
|
346
|
-
}
|
|
308
|
+
conversationRuntime,
|
|
309
|
+
conversationAccess,
|
|
310
|
+
conversationDataSchema,
|
|
311
|
+
conversationConfigurationSchema,
|
|
312
|
+
conversationSelectionSchema,
|
|
313
|
+
conversationReplacementSchema,
|
|
314
|
+
workspaceScopeSupport,
|
|
315
|
+
config
|
|
316
|
+
}) {
|
|
347
317
|
const configuredSurfaces = configuredAssistantSurfaces(config);
|
|
348
318
|
const runtimeSurfaces = [...new Set(configuredSurfaces.map((entry) => entry.targetSurfaceId))];
|
|
349
319
|
const settingsSurfaces = [...new Set(configuredSurfaces.map((entry) => entry.settingsSurfaceId))];
|
|
350
320
|
if (runtimeSurfaces.length === 0 || settingsSurfaces.length === 0) return [];
|
|
351
321
|
|
|
352
322
|
if (conversationRuntime) {
|
|
323
|
+
const conversationOperations = createConversationOperations({
|
|
324
|
+
conversationRuntime,
|
|
325
|
+
config,
|
|
326
|
+
workspaceScopeSupport,
|
|
327
|
+
conversationDataSchema
|
|
328
|
+
});
|
|
353
329
|
const conversationActions = createConversationActions({
|
|
354
|
-
|
|
355
|
-
|
|
330
|
+
conversationOperations, conversationDataSchema, conversationConfigurationSchema, conversationSelectionSchema, conversationReplacementSchema,
|
|
331
|
+
runtimeSurfaces
|
|
356
332
|
});
|
|
357
333
|
return Object.freeze([
|
|
358
334
|
...conversationActions.map((definition) => {
|
|
@@ -368,64 +344,49 @@ function createAssistantActions({
|
|
|
368
344
|
}
|
|
369
345
|
|
|
370
346
|
return Object.freeze([
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
async execute(input, context, deps) {
|
|
383
|
-
return chatService.streamChat(input, {
|
|
347
|
+
Object.freeze({
|
|
348
|
+
id: actionIds.chatStream,
|
|
349
|
+
version: 1,
|
|
350
|
+
kind: "stream",
|
|
351
|
+
channels: ["api", "internal"],
|
|
352
|
+
surfaces: runtimeSurfaces,
|
|
353
|
+
permission: {
|
|
354
|
+
require: "authenticated"
|
|
355
|
+
},
|
|
356
|
+
input: runtimeChatStreamInputValidator,
|
|
357
|
+
execute: (input, context, deps) => chatService.streamChat(input, {
|
|
384
358
|
context,
|
|
385
359
|
streamWriter: deps.streamWriter,
|
|
386
360
|
abortSignal: deps.abortSignal
|
|
387
|
-
})
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
require: "authenticated"
|
|
417
|
-
},
|
|
418
|
-
input: runtimeConversationMessagesListInputValidator,
|
|
419
|
-
output: null,
|
|
420
|
-
idempotency: "none",
|
|
421
|
-
async execute(input, context) {
|
|
422
|
-
return returnJsonApiData(await chatService.getConversationMessages(input.conversationId, input.query, {
|
|
423
|
-
context,
|
|
424
|
-
input
|
|
425
|
-
}));
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
...createAssistantSettingsActions({ assistantConfigService, settingsSurfaces })
|
|
361
|
+
})
|
|
362
|
+
}),
|
|
363
|
+
Object.freeze({
|
|
364
|
+
id: actionIds.conversationsList,
|
|
365
|
+
version: 1,
|
|
366
|
+
kind: "query",
|
|
367
|
+
channels: ["api", "internal"],
|
|
368
|
+
surfaces: runtimeSurfaces,
|
|
369
|
+
permission: {
|
|
370
|
+
require: "authenticated"
|
|
371
|
+
},
|
|
372
|
+
input: runtimeConversationsListInputValidator,
|
|
373
|
+
output: null,
|
|
374
|
+
execute: (input, context) => listConversationsDocument(chatService, input, context)
|
|
375
|
+
}),
|
|
376
|
+
Object.freeze({
|
|
377
|
+
id: actionIds.conversationMessagesList,
|
|
378
|
+
version: 1,
|
|
379
|
+
kind: "query",
|
|
380
|
+
channels: ["api", "internal"],
|
|
381
|
+
surfaces: runtimeSurfaces,
|
|
382
|
+
permission: {
|
|
383
|
+
require: "authenticated"
|
|
384
|
+
},
|
|
385
|
+
input: runtimeConversationMessagesListInputValidator,
|
|
386
|
+
output: null,
|
|
387
|
+
execute: (input, context) => listConversationMessagesDocument(chatService, input, context)
|
|
388
|
+
}),
|
|
389
|
+
...createAssistantSettingsActions({ assistantConfigService, settingsSurfaces })
|
|
429
390
|
]);
|
|
430
391
|
}
|
|
431
392
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { requireAssistantSurface } from "./support/assistantSurface.js";
|
|
3
|
+
|
|
4
|
+
/** @typedef {import("./types.js").ConversationRuntime} ConversationRuntime */
|
|
5
|
+
/** @typedef {import("./types.js").WorkspaceScopeSupport} WorkspaceScopeSupport */
|
|
6
|
+
|
|
7
|
+
/** A subscription caller that does not say whether it is still current stays current. */
|
|
8
|
+
function alwaysCurrent() {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A subscription caller that does not follow its stages ignores them. */
|
|
13
|
+
function ignoreStage() {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* What the conversation actions do: each opens the action's conversation in its
|
|
17
|
+
* context, on the conversation's workspace when its surface requires one, and
|
|
18
|
+
* calls one conversation method.
|
|
19
|
+
* @param {object} dependencies
|
|
20
|
+
* @param {ConversationRuntime} dependencies.conversationRuntime
|
|
21
|
+
* @param {Record<string, any>} dependencies.config
|
|
22
|
+
* @param {WorkspaceScopeSupport | null} dependencies.workspaceScopeSupport
|
|
23
|
+
* @param {object | null} dependencies.conversationDataSchema The application's declared message data, when it declares one.
|
|
24
|
+
*/
|
|
25
|
+
function createConversationOperations({ conversationRuntime, config, workspaceScopeSupport, conversationDataSchema }) {
|
|
26
|
+
async function openConversation(input, context) {
|
|
27
|
+
const assistantSurface = requireAssistantSurface(config, input.targetSurfaceId);
|
|
28
|
+
let conversationContext = context;
|
|
29
|
+
if (assistantSurface.runtimeSurfaceRequiresWorkspace) {
|
|
30
|
+
if (!workspaceScopeSupport) {
|
|
31
|
+
throw new Error("Assistant conversation actions require workspace server scope support.");
|
|
32
|
+
}
|
|
33
|
+
const workspace = workspaceScopeSupport.resolveWorkspace(context, { workspaceSlug: input.workspaceSlug });
|
|
34
|
+
if (!workspace?.id) throw new AppError(409, "Workspace selection required.", { code: "assistant.runtime.workspace.selectionRequired" });
|
|
35
|
+
conversationContext = { ...context, workspace };
|
|
36
|
+
}
|
|
37
|
+
return conversationRuntime.open({ id: input.conversationId, context: conversationContext });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function read(input, context) {
|
|
41
|
+
const conversation = await openConversation(input, context);
|
|
42
|
+
const options = {};
|
|
43
|
+
for (const key of ["beforeTurnId", "limit"]) {
|
|
44
|
+
if (Object.hasOwn(input, key)) options[key] = input[key];
|
|
45
|
+
}
|
|
46
|
+
return Object.keys(options).length ? conversation.read(options) : conversation.read();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function send(input, context) {
|
|
50
|
+
const conversation = await openConversation(input, context);
|
|
51
|
+
return conversation.send({
|
|
52
|
+
messageId: input.messageId,
|
|
53
|
+
text: input.text,
|
|
54
|
+
...(conversationDataSchema && Object.hasOwn(input, "data") ? { data: input.data } : {}),
|
|
55
|
+
...(Object.hasOwn(input, "attachmentIds") ? { attachmentIds: input.attachmentIds } : {}),
|
|
56
|
+
...(Object.hasOwn(input, "steer") ? { steer: input.steer } : {})
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function cancel(input, context) {
|
|
61
|
+
const conversation = await openConversation(input, context);
|
|
62
|
+
return conversation.cancel();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Opens the conversation, attaches the caller's observer and answers with its
|
|
67
|
+
* snapshot. The caller's callbacks come with each call.
|
|
68
|
+
* @param {object} input
|
|
69
|
+
* @param {object} context
|
|
70
|
+
* @param {{ onEvent: (event: object) => void, onRelease: (release: () => void) => void, isCurrent?: () => boolean,
|
|
71
|
+
* onStage?: (stage: string) => void }} deps
|
|
72
|
+
*/
|
|
73
|
+
async function subscribe(input, context, { onEvent, onRelease, isCurrent = alwaysCurrent, onStage = ignoreStage }) {
|
|
74
|
+
if (isCurrent() === false) return null;
|
|
75
|
+
onStage("conversation-open");
|
|
76
|
+
const conversation = await openConversation(input, context);
|
|
77
|
+
if (isCurrent() === false) return null;
|
|
78
|
+
onStage("observer-attach");
|
|
79
|
+
const release = await conversation.subscribe(onEvent);
|
|
80
|
+
try {
|
|
81
|
+
onRelease(release);
|
|
82
|
+
if (isCurrent() === false) return null;
|
|
83
|
+
onStage("snapshot-read");
|
|
84
|
+
return await conversation.read();
|
|
85
|
+
} catch (error) {
|
|
86
|
+
release();
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function inspectDelivery(input, context) {
|
|
92
|
+
const conversation = await openConversation(input, context);
|
|
93
|
+
return conversation.inspectDelivery({ messageId: input.messageId });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function cancelDelivery(input, context) {
|
|
97
|
+
const conversation = await openConversation(input, context);
|
|
98
|
+
return conversation.cancelDelivery({ messageId: input.messageId });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function readGoal(input, context) {
|
|
102
|
+
return (await openConversation(input, context)).readGoal();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function updateGoal(input, context) {
|
|
106
|
+
const conversation = await openConversation(input, context);
|
|
107
|
+
const goal = { action: input.action, expectedSegmentId: input.expectedSegmentId };
|
|
108
|
+
for (const key of ["expectedGoalId", "messageId", "objective", "tokenBudget", "attachmentIds"]) {
|
|
109
|
+
if (Object.hasOwn(input, key)) goal[key] = input[key];
|
|
110
|
+
}
|
|
111
|
+
return conversation.updateGoal(goal);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function configure(input, context) {
|
|
115
|
+
return (await openConversation(input, context)).configure(input.configuration);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function select(input, context) {
|
|
119
|
+
return (await openConversation(input, context)).select(input.selection);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function replace(input, context) {
|
|
123
|
+
return (await openConversation(input, context)).replace(input.replacement);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return Object.freeze({
|
|
127
|
+
read,
|
|
128
|
+
send,
|
|
129
|
+
cancel,
|
|
130
|
+
subscribe,
|
|
131
|
+
inspectDelivery,
|
|
132
|
+
cancelDelivery,
|
|
133
|
+
readGoal,
|
|
134
|
+
updateGoal,
|
|
135
|
+
configure,
|
|
136
|
+
select,
|
|
137
|
+
replace
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { createConversationOperations };
|