@jskit-ai/assistant-runtime 0.2.28 → 0.2.30
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/CHANGELOG.md +7 -0
- package/README.md +6 -3
- package/package.json +16 -10
- package/patterns/assistant-surface/PATTERN.md +5 -2
- package/src/client/components/AssistantSettingsClientElement.vue +5 -3
- package/src/server/AssistantProvider.js +8 -13
- package/src/server/AssistantStoreFeature.js +59 -0
- package/src/server/actions.js +5 -5
- package/src/server/createAssistantRuntime.js +2 -1
- package/src/server/createAssistantSettingsActions.js +17 -9
- package/src/server/index.js +1 -0
- package/src/server/registerConversationSubscriptions.js +5 -2
- package/src/server/registerRoutes.js +11 -8
- package/src/server/registerSettingsRoutes.js +4 -4
- package/src/server/repositories/assistantConfigRepository.js +52 -67
- package/src/server/repositories/assistantResources.js +110 -0
- package/src/server/repositories/conversationsRepository.js +164 -147
- package/src/server/repositories/messagesRepository.js +136 -87
- package/src/server/repositories/repositoryPersistenceUtils.js +11 -22
- package/src/server/repositories/turnRequestsRepository.js +50 -16
- package/src/server/services/assistantConfigService.js +22 -0
- package/src/server/services/chatService.js +15 -7
- package/src/server/services/transcriptService.js +3 -3
- package/src/server/sqlConversationStorage.js +23 -19
- package/src/server/types.js +1 -0
- package/test/assistantIntegrationBoundary.test.js +2 -0
- package/test/assistantSettings.browser.test.js +23 -2
- package/test/assistantSettingsAccess.test.js +46 -1
- package/test/chatServiceLifecycle.test.js +32 -1
- package/test/featureRuntime.test.js +17 -3
- package/test/repositories.mysql.test.js +474 -0
- package/test/repositoryContracts.test.js +13 -13
- package/test/sqlConversationStorage.mysql.test.js +9 -6
- package/test/sqlConversationStorage.test.js +27 -20
- package/test/support/assistantStore.js +39 -0
- package/test/support/memoryAssistantRows.js +11 -4
- package/test/turnRequests.test.js +37 -14
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- The conversations and messages repositories hand their handle calls only the caller's transaction, with their own context and settings: a caller's other settings (a format, `missing`, `returning`, `fields`) no longer ride into every read and write they make. They take `passedOn` from `@jskit-ai/resource-core/shared/callOptions`, so `@jskit-ai/assistant-runtime/server` still loads on file storage, where `@jskit-ai/resource-runtime` (an optional peer) is not installed.
|
|
6
|
+
- A database error in an assistant turn reaches the client only as the kernel's generic answer (`hideDatabaseError`: 409 `kernel.database.concurrentWrite` when the database refused the transaction for a concurrent one, else 500). A failure after the stream started was sent with its own message, which for a database error is the statement and its values, and the same message was saved with the turn and replayed, as an `AppError`, to a client that sent the message again; the chat route's error before the stream and a conversation subscription's acknowledgement carried the driver's code. Each now sends and saves the stand-in and logs the whole error; `createChatService` takes the runtime's `logger`.
|
|
7
|
+
- The assistant settings pane shows **Save** only to people the server lets change the settings. It offered Save to anyone who could read them, so a person without the permission (an employee, say) saw Save and was refused with `assistant.runtime.settings.updateForbidden` when they used it. The settings read now carries `canUpdate`, answered by the new `assistant.settings` service method `canUpdateSettings(input, { context })`, which runs the same check as `updateSettings` (an app's `assistantServer.<surface>.settingsUpdatePermission`, else `workspace.settings.update`, the console owner or `assistant.settings.update`) and answers false where that check refuses; a saved change carries `canUpdate: true`. Without it, the pane shows the instructions read-only, with no Save in the app bar. Apps change nothing; an app that supplies its own `assistant.settings` service adds `canUpdateSettings`.
|
|
8
|
+
- A conversation write through the SQL storage issues 8 statements on SQLite where it issued 12, whatever the conversation's length: the conversation and its transcript state come from one read, new messages take the next seq after the one the load read under the conversation's lock instead of reading it again, and one UPDATE writes the transcript state with the record's changes instead of an update, a read-back and a second update. A read issues 4 where it issued 5. `assistant.repositories`' conversations replace `readTranscriptState(id)` with `findWithTranscriptState(id, options)`, which answers `{ conversation, revision, summary }` (under a row lock with `forUpdate`), and `writeTranscriptState` takes the record's `changes` beside the revision and summary.
|
|
9
|
+
- `AssistantFeature` with neither `assistant.conversations` nor `AssistantStoreFeature` fails at startup naming `AssistantStoreFeature`, its import path and where to register it.
|
|
10
|
+
- `assistant.repositories`' messages gain `findNewestAssistantByUserId(userId, { kind }, options)`: the newest assistant message (latest `createdAt`, then highest id) in any of one user's conversations, in every workspace and surface, or null; `kind` narrows it, such as `"chat"` for a reply. It costs two reads for a user with up to 1,000 conversations. The messages table resource gains `role` and `kind` filters and a `conversationIds` list filter.
|
|
5
11
|
- `@jskit-ai/assistant-runtime/client/providers/AssistantClientProvider` exports the client provider by itself, so the client bootstrap loads it without the assistant's elements and what they import.
|
|
6
12
|
- `subscribeAssistantConversation()` takes `noticeMs`, how long an unanswered subscription waits before the loading notice. It stays ten seconds; tests pass a shorter one, so the real-socket proof of a slow subscription no longer waits ten seconds.
|
|
13
|
+
- Reads its operations from assistant-core's `assistantContract` and `assistantConfigContract`.
|
|
7
14
|
- The assistant settings are a settings pane: the app bar shows their title and Save (a primary bar action that submits the form, with the card's loading and disabled states), and the card no longer has a Save row at its bottom. The button reads **Save** (`jskit.assistant.runtime.settings.save`, was "Save assistant settings"), since the bar's title names the settings. Visual only: what saving does is unchanged.
|
|
8
15
|
- A browser test (`test/assistantSettings.browser.test.js`, fixture `fixtures/settings/`, run in CI) renders the settings pane in the shell's `ShellLayout` at 390 and 1280 px: the bar's title is the page's one `h1`, its **Save** is a submit button for `assistant-settings-form` that sends the edited prompt and shows its loading state until the save answers, a refused save shows the prompt's field message and reports the failure, and workspace settings show their note and no Save until the address names a workspace.
|
|
9
16
|
- The assistant surface pattern's icon is `$smartToy`, and the fixtures render with `@jskit-ai/material-web`.
|
package/README.md
CHANGED
|
@@ -12,14 +12,17 @@ Two hosting modes share that runtime:
|
|
|
12
12
|
`assistant.conversations` capability. No database is required.
|
|
13
13
|
- **Built-in database-backed surface.** The optional SQL integration adds
|
|
14
14
|
database conversations, per-surface settings and the request-streaming chat
|
|
15
|
-
route.
|
|
16
|
-
|
|
15
|
+
route. The application registers `AssistantStoreFeature` in its server
|
|
16
|
+
feature list and installs `@jskit-ai/resource-runtime`: the feature owns the
|
|
17
|
+
assistant's four tables as table resources and provides their repositories as
|
|
18
|
+
`assistant.repositories`, which `AssistantFeature` uses. It needs
|
|
19
|
+
`npm run db:migrate` and is composed by the `assistant/assistant-surface` pattern.
|
|
17
20
|
|
|
18
21
|
Main exports by subpath:
|
|
19
22
|
|
|
20
23
|
| Subpath | Provides |
|
|
21
24
|
| --- | --- |
|
|
22
|
-
| `@jskit-ai/assistant-runtime/server` | `AssistantFeature`, `createAssistantActions`, `registerAssistantRoutes`, `registerConversationSubscriptions`, `registerFastifyConversations`, `createSurfaceAwareToolCatalog` |
|
|
25
|
+
| `@jskit-ai/assistant-runtime/server` | `AssistantFeature`, `AssistantStoreFeature`, `createAssistantActions`, `registerAssistantRoutes`, `registerConversationSubscriptions`, `registerFastifyConversations`, `createSurfaceAwareToolCatalog` |
|
|
23
26
|
| `@jskit-ai/assistant-runtime/server/actionIds` | Assistant action identifiers |
|
|
24
27
|
| `@jskit-ai/assistant-runtime/client` | `AssistantConversationClientElement`, `AssistantSurfaceClientElement`, `AssistantSettingsClientElement`, `useAssistantConversation`, `configureAssistantConversations` |
|
|
25
28
|
| `@jskit-ai/assistant-runtime/shared` | Assistant surface configuration helpers |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-runtime",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "jskit test"
|
|
@@ -15,19 +15,21 @@
|
|
|
15
15
|
"./locales/en.json": "./locales/en.json"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@jskit-ai/assistant-core": "0.2.
|
|
18
|
+
"@jskit-ai/assistant-core": "0.2.28",
|
|
19
|
+
"@jskit-ai/resource-core": "0.1.205",
|
|
19
20
|
"json-rest-schema": "^1.0.17"
|
|
20
21
|
},
|
|
21
22
|
"peerDependencies": {
|
|
22
|
-
"@jskit-ai/http-runtime": "0.2.
|
|
23
|
-
"@jskit-ai/http-web": "0.2.
|
|
24
|
-
"@jskit-ai/kernel": "0.2.
|
|
25
|
-
"@jskit-ai/realtime": "0.2.
|
|
26
|
-
"@jskit-ai/shell-web": "0.2.
|
|
23
|
+
"@jskit-ai/http-runtime": "0.2.14",
|
|
24
|
+
"@jskit-ai/http-web": "0.2.22",
|
|
25
|
+
"@jskit-ai/kernel": "0.2.14",
|
|
26
|
+
"@jskit-ai/realtime": "0.2.20",
|
|
27
|
+
"@jskit-ai/shell-web": "0.2.17",
|
|
27
28
|
"@tanstack/vue-query": "^5.90.5",
|
|
28
29
|
"vue": "^3.5.13",
|
|
29
30
|
"vuetify": "^4.0.0",
|
|
30
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
31
|
+
"@jskit-ai/database-runtime": "0.1.263",
|
|
32
|
+
"@jskit-ai/resource-runtime": "0.2.12"
|
|
31
33
|
},
|
|
32
34
|
"description": "Capability-based assistant runtime with per-surface chat, transcripts, settings, and action tools.",
|
|
33
35
|
"jskit": {
|
|
@@ -103,7 +105,7 @@
|
|
|
103
105
|
},
|
|
104
106
|
{
|
|
105
107
|
"subpath": "./server",
|
|
106
|
-
"summary": "Provides conversation routes, authorized shared realtime subscriptions, explicit Fastify hosting shorthand, optional database integration, the per-surface assistant tool catalogue and the person-context prompt a host composes into its conversations' instructions."
|
|
108
|
+
"summary": "Provides conversation routes, authorized shared realtime subscriptions, explicit Fastify hosting shorthand, optional database integration (AssistantStoreFeature owns its tables through runtime.resources and provides assistant.repositories), the per-surface assistant tool catalogue and the person-context prompt a host composes into its conversations' instructions."
|
|
107
109
|
},
|
|
108
110
|
{
|
|
109
111
|
"subpath": "./server/types",
|
|
@@ -130,9 +132,13 @@
|
|
|
130
132
|
},
|
|
131
133
|
"@jskit-ai/database-runtime": {
|
|
132
134
|
"optional": true
|
|
135
|
+
},
|
|
136
|
+
"@jskit-ai/resource-runtime": {
|
|
137
|
+
"optional": true
|
|
133
138
|
}
|
|
134
139
|
},
|
|
135
140
|
"devDependencies": {
|
|
136
|
-
"@jskit-ai/material-web": "0.1.3"
|
|
141
|
+
"@jskit-ai/material-web": "0.1.3",
|
|
142
|
+
"@jskit-ai/resource-runtime": "0.2.12"
|
|
137
143
|
}
|
|
138
144
|
}
|
|
@@ -48,8 +48,11 @@ in source or a questionnaire transcript.
|
|
|
48
48
|
|
|
49
49
|
Use `AssistantSurfaceClientElement` and `AssistantSettingsClientElement` from
|
|
50
50
|
`@jskit-ai/assistant-runtime/client`. Install the runtime package through the
|
|
51
|
-
normal package-capability path.
|
|
52
|
-
|
|
51
|
+
normal package-capability path. Install `@jskit-ai/resource-runtime` and
|
|
52
|
+
register `AssistantStoreFeature` from `@jskit-ai/assistant-runtime/server` in
|
|
53
|
+
the ordinary server feature list; it owns the assistant's tables and provides the
|
|
54
|
+
repositories the database-backed surface uses. Configure `assistantSurfaces` in
|
|
55
|
+
public config and `assistantServer` in server config.
|
|
53
56
|
|
|
54
57
|
## Example files
|
|
55
58
|
|
|
@@ -41,7 +41,7 @@ import { getClientAppConfig } from "@jskit-ai/kernel/client";
|
|
|
41
41
|
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
42
42
|
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
43
43
|
import { assistantHttpClient, createAssistantApi, AssistantSettingsFormCard } from "@jskit-ai/assistant-core/client";
|
|
44
|
-
import {
|
|
44
|
+
import { assistantConfigContract, assistantSettingsQueryKey, buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
|
|
45
45
|
import ShellPageAction from "@jskit-ai/shell-web/client/components/ShellPageAction";
|
|
46
46
|
import ShellPageActions from "@jskit-ai/shell-web/client/components/ShellPageActions";
|
|
47
47
|
import { useShellPageTitle } from "@jskit-ai/shell-web/client/composables/useShellPageTitle";
|
|
@@ -195,7 +195,7 @@ async function submit() {
|
|
|
195
195
|
clearFieldErrors();
|
|
196
196
|
|
|
197
197
|
const validation = validateOperationSection({
|
|
198
|
-
operation:
|
|
198
|
+
operation: assistantConfigContract.operations.patch,
|
|
199
199
|
section: "body",
|
|
200
200
|
value: {
|
|
201
201
|
systemPrompt: form.systemPrompt
|
|
@@ -221,7 +221,9 @@ async function submit() {
|
|
|
221
221
|
|
|
222
222
|
const addEdit = computed(() => ({
|
|
223
223
|
canView: Boolean(assistantSurface.value) && hasScope.value,
|
|
224
|
-
|
|
224
|
+
// The server says whether this person may change the settings (it checks the same permission when they save); anyone
|
|
225
|
+
// else sees them read-only, without Save.
|
|
226
|
+
canSave: Boolean(assistantSurface.value) && hasScope.value && settingsQuery.data.value?.canUpdate === true,
|
|
225
227
|
loadError: loadError.value,
|
|
226
228
|
isInitialLoading: Boolean(settingsQuery.isLoading.value),
|
|
227
229
|
isRefetching: Boolean(settingsQuery.isFetching.value && !settingsQuery.isLoading.value),
|
|
@@ -51,7 +51,7 @@ const AssistantFeature = defineFeature({
|
|
|
51
51
|
conversationRuntime: "assistant.conversations",
|
|
52
52
|
conversationAccess: "assistant.conversation.access",
|
|
53
53
|
assistantConfigService: "assistant.settings",
|
|
54
|
-
|
|
54
|
+
repositories: "assistant.repositories",
|
|
55
55
|
realtime: "runtime.realtime",
|
|
56
56
|
aiConnections: "integrations.ai",
|
|
57
57
|
attachments: "assistant.attachments",
|
|
@@ -77,14 +77,14 @@ const AssistantFeature = defineFeature({
|
|
|
77
77
|
// composes assistant.personContext into them itself, with createPersonContextPrompt.
|
|
78
78
|
assistant = createHostedAssistant({ conversationRuntime, assistantConfigService: assistantConfigService || null });
|
|
79
79
|
} else {
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// The database-backed assistant keeps its rows through AssistantStoreFeature's repositories.
|
|
81
|
+
if (!dependencies.repositories) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"AssistantFeature keeps conversations in assistant.repositories, which no feature provides: register AssistantStoreFeature " +
|
|
84
|
+
"from @jskit-ai/assistant-runtime/server in your server providers, or provide assistant.conversations (createConversationRuntime())."
|
|
85
|
+
);
|
|
82
86
|
}
|
|
83
87
|
const { createAssistantRuntime } = await import("./createAssistantRuntime.js");
|
|
84
|
-
const { createRepository: createAssistantConfigRepository } = await import("./repositories/assistantConfigRepository.js");
|
|
85
|
-
const { createRepository: createConversationsRepository } = await import("./repositories/conversationsRepository.js");
|
|
86
|
-
const { createRepository: createMessagesRepository } = await import("./repositories/messagesRepository.js");
|
|
87
|
-
const { createRepository: createTurnRequestsRepository } = await import("./repositories/turnRequestsRepository.js");
|
|
88
88
|
assistant = createAssistantRuntime({
|
|
89
89
|
actionCatalogue,
|
|
90
90
|
config: dependencies.config,
|
|
@@ -95,12 +95,7 @@ const AssistantFeature = defineFeature({
|
|
|
95
95
|
personContext: dependencies.personContext ?? NO_PERSON_CONTEXT,
|
|
96
96
|
logger: dependencies.logger,
|
|
97
97
|
workspaces: dependencies.workspaces || null,
|
|
98
|
-
repositories:
|
|
99
|
-
config: createAssistantConfigRepository(dependencies.database.knex),
|
|
100
|
-
conversations: createConversationsRepository(dependencies.database.knex),
|
|
101
|
-
messages: createMessagesRepository(dependencies.database.knex),
|
|
102
|
-
turnRequests: createTurnRequestsRepository(dependencies.database.knex)
|
|
103
|
-
}
|
|
98
|
+
repositories: dependencies.repositories
|
|
104
99
|
});
|
|
105
100
|
}
|
|
106
101
|
registerRoutes(dependencies.http.router, {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { defineFeature } from "@jskit-ai/kernel/server/features";
|
|
2
|
+
import {
|
|
3
|
+
assistantConfigResource,
|
|
4
|
+
assistantConversationsResource,
|
|
5
|
+
assistantMessagesResource,
|
|
6
|
+
assistantTurnRequestsResource
|
|
7
|
+
} from "./repositories/assistantResources.js";
|
|
8
|
+
import { createRepository as createConfigRepository } from "./repositories/assistantConfigRepository.js";
|
|
9
|
+
import { createRepository as createConversationsRepository } from "./repositories/conversationsRepository.js";
|
|
10
|
+
import { createRepository as createMessagesRepository } from "./repositories/messagesRepository.js";
|
|
11
|
+
import { createRepository as createTurnRequestsRepository } from "./repositories/turnRequestsRepository.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The repositories of the database-backed assistant over the tables this feature owns.
|
|
15
|
+
* @param {object} dependencies
|
|
16
|
+
* @param {Record<string, import("@jskit-ai/resource-runtime/server/types").ResourceHandle>} dependencies.resources
|
|
17
|
+
* @param {(reason: string) => import("@jskit-ai/resource-runtime/server/types").SystemContext} dependencies.systemContext
|
|
18
|
+
*/
|
|
19
|
+
function createAssistantRepositories({ resources, systemContext }) {
|
|
20
|
+
return Object.freeze({
|
|
21
|
+
config: createConfigRepository({ assistantConfig: resources.assistantConfig, systemContext }),
|
|
22
|
+
conversations: createConversationsRepository({ conversations: resources.assistantConversations, systemContext }),
|
|
23
|
+
messages: createMessagesRepository({
|
|
24
|
+
messages: resources.assistantMessages,
|
|
25
|
+
conversations: resources.assistantConversations,
|
|
26
|
+
systemContext
|
|
27
|
+
}),
|
|
28
|
+
turnRequests: createTurnRequestsRepository({ turnRequests: resources.assistantTurnRequests, systemContext })
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The database-backed assistant's tables: settings, conversations, messages and
|
|
33
|
+
// request receipts. An application that keeps its assistant in the database
|
|
34
|
+
// registers this feature beside AssistantFeature, which takes its repositories;
|
|
35
|
+
// an application that supplies assistant.conversations does not.
|
|
36
|
+
const AssistantStoreFeature = defineFeature({
|
|
37
|
+
id: "assistant.store",
|
|
38
|
+
domain: "assistant",
|
|
39
|
+
owns: [
|
|
40
|
+
assistantConfigResource,
|
|
41
|
+
assistantConversationsResource,
|
|
42
|
+
assistantMessagesResource,
|
|
43
|
+
assistantTurnRequestsResource
|
|
44
|
+
],
|
|
45
|
+
systemScope: "the assistant reads and writes its rows by the actor, workspace and surface its own access checks resolved, and keeps global settings that every workspace shares",
|
|
46
|
+
provides: {
|
|
47
|
+
repositories: "assistant.repositories"
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* @param {object} dependencies
|
|
51
|
+
* @param {Record<string, import("@jskit-ai/resource-runtime/server/types").ResourceHandle>} dependencies.resources
|
|
52
|
+
* @param {(reason: string) => import("@jskit-ai/resource-runtime/server/types").SystemContext} dependencies.systemContext
|
|
53
|
+
*/
|
|
54
|
+
setup({ resources, systemContext }) {
|
|
55
|
+
return { repositories: createAssistantRepositories({ resources, systemContext }) };
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export { AssistantStoreFeature };
|
package/src/server/actions.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
8
8
|
import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
assistantContract
|
|
11
11
|
} from "@jskit-ai/assistant-core/shared";
|
|
12
12
|
import { actionIds } from "./actionIds.js";
|
|
13
13
|
import { createAssistantSettingsActions } from "./createAssistantSettingsActions.js";
|
|
@@ -40,7 +40,7 @@ const runtimeConversationsListQueryInputValidator = deepFreeze({
|
|
|
40
40
|
query: {
|
|
41
41
|
type: "object",
|
|
42
42
|
required: false,
|
|
43
|
-
schema:
|
|
43
|
+
schema: assistantContract.operations.conversationsList.query.schema
|
|
44
44
|
}
|
|
45
45
|
}),
|
|
46
46
|
mode: "patch"
|
|
@@ -59,7 +59,7 @@ const runtimeConversationMessagesListQueryInputValidator = deepFreeze({
|
|
|
59
59
|
query: {
|
|
60
60
|
type: "object",
|
|
61
61
|
required: false,
|
|
62
|
-
schema:
|
|
62
|
+
schema: assistantContract.operations.conversationMessagesList.query.schema
|
|
63
63
|
}
|
|
64
64
|
}),
|
|
65
65
|
mode: "patch"
|
|
@@ -68,7 +68,7 @@ const runtimeConversationMessagesListQueryInputValidator = deepFreeze({
|
|
|
68
68
|
const runtimeConversationMessagesListInputValidator = composeSchemaDefinitions(
|
|
69
69
|
[
|
|
70
70
|
assistantTargetSurfaceInputValidator,
|
|
71
|
-
|
|
71
|
+
assistantContract.operations.conversationMessagesList.params,
|
|
72
72
|
runtimeConversationMessagesListQueryInputValidator
|
|
73
73
|
],
|
|
74
74
|
{
|
|
@@ -80,7 +80,7 @@ const runtimeConversationMessagesListInputValidator = composeSchemaDefinitions(
|
|
|
80
80
|
const runtimeChatStreamInputValidator = composeSchemaDefinitions(
|
|
81
81
|
[
|
|
82
82
|
assistantTargetSurfaceInputValidator,
|
|
83
|
-
|
|
83
|
+
assistantContract.operations.chatStream.body
|
|
84
84
|
],
|
|
85
85
|
{
|
|
86
86
|
mode: "create",
|
|
@@ -129,7 +129,8 @@ function createAssistantRuntime({
|
|
|
129
129
|
assistantConfigService: configService,
|
|
130
130
|
personContextPrompt: createPersonContextPrompt({ personContext, logger }),
|
|
131
131
|
appConfig: config,
|
|
132
|
-
workspaceScopeSupport
|
|
132
|
+
workspaceScopeSupport,
|
|
133
|
+
logger
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
return Object.freeze({
|
|
@@ -2,7 +2,7 @@ import { createSchema } from "json-rest-schema";
|
|
|
2
2
|
import { composeSchemaDefinitions } from "@jskit-ai/kernel/shared/validators";
|
|
3
3
|
import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
4
4
|
import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
|
|
5
|
-
import {
|
|
5
|
+
import { assistantConfigContract } from "@jskit-ai/assistant-core/shared";
|
|
6
6
|
import { actionIds } from "./actionIds.js";
|
|
7
7
|
import { assistantTargetSurfaceInputValidator } from "./inputSchemas.js";
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ const settingsUpdatePatchInputValidator = deepFreeze({
|
|
|
15
15
|
patch: {
|
|
16
16
|
type: "object",
|
|
17
17
|
required: true,
|
|
18
|
-
schema:
|
|
18
|
+
schema: assistantConfigContract.operations.patch.body.schema
|
|
19
19
|
}
|
|
20
20
|
}),
|
|
21
21
|
mode: "patch"
|
|
@@ -30,27 +30,35 @@ const settingsUpdateInputValidator = composeSchemaDefinitions(
|
|
|
30
30
|
);
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* The JSON:API document of a surface's assistant settings.
|
|
33
|
+
* The JSON:API document of a surface's assistant settings, with whether the reader may change them (`canUpdate`).
|
|
34
34
|
* @param {AssistantConfigService} assistantConfigService
|
|
35
35
|
* @param {object} input
|
|
36
36
|
* @param {object} context
|
|
37
37
|
*/
|
|
38
38
|
async function readSettingsDocument(assistantConfigService, input, context) {
|
|
39
|
-
|
|
39
|
+
const settings = await assistantConfigService.getSettings(input, {
|
|
40
40
|
context
|
|
41
|
-
})
|
|
41
|
+
});
|
|
42
|
+
const canUpdate = await assistantConfigService.canUpdateSettings(input, {
|
|
43
|
+
context
|
|
44
|
+
});
|
|
45
|
+
return returnJsonApiData({ ...settings, canUpdate });
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
/**
|
|
45
|
-
* The JSON:API document of a surface's assistant settings after the input's patch
|
|
49
|
+
* The JSON:API document of a surface's assistant settings after the input's patch, which only someone who may change
|
|
50
|
+
* them gets.
|
|
46
51
|
* @param {AssistantConfigService} assistantConfigService
|
|
47
52
|
* @param {object} input
|
|
48
53
|
* @param {object} context
|
|
49
54
|
*/
|
|
50
55
|
async function updateSettingsDocument(assistantConfigService, input, context) {
|
|
51
|
-
return returnJsonApiData(
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
return returnJsonApiData({
|
|
57
|
+
...(await assistantConfigService.updateSettings(input, input.patch, {
|
|
58
|
+
context
|
|
59
|
+
})),
|
|
60
|
+
canUpdate: true
|
|
61
|
+
});
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
/**
|
package/src/server/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AssistantFeature } from "./AssistantProvider.js";
|
|
2
|
+
export { AssistantStoreFeature } from "./AssistantStoreFeature.js";
|
|
2
3
|
export { createAssistantActions } from "./actions.js";
|
|
3
4
|
export { assistantConversationGoalBodyValidator } from "./inputSchemas.js";
|
|
4
5
|
export { registerRoutes as registerAssistantRoutes } from "./registerRoutes.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { resolveActorId } from "@jskit-ai/kernel/server/actions/actionContributorHelpers";
|
|
3
3
|
import { attachDirectRequestActionExecutor } from "@jskit-ai/kernel/server/http";
|
|
4
|
-
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
4
|
+
import { AppError, hideDatabaseError } from "@jskit-ai/kernel/server/runtime";
|
|
5
5
|
import { silentLogger } from "@jskit-ai/kernel/server/runtime/logger";
|
|
6
6
|
import { actionIds } from "./actionIds.js";
|
|
7
7
|
import { resolveRouteRequestState } from "./support/assistantRouteContext.js";
|
|
@@ -210,7 +210,10 @@ function registerConversationSubscriptions({ realtime, events, actions, config =
|
|
|
210
210
|
acknowledge({ ok: true, subscriptionId, streamEpoch: subscription.epoch, state });
|
|
211
211
|
// Sending an acknowledgement does not prove that a timed-out client received it.
|
|
212
212
|
onStage("acknowledgement-sent");
|
|
213
|
-
} catch (
|
|
213
|
+
} catch (thrown) {
|
|
214
|
+
// A database error is acknowledged as its stand-in (a generic 409 or 500), and its detail goes to the log.
|
|
215
|
+
const error = hideDatabaseError(thrown);
|
|
216
|
+
if (error !== thrown) logger.error({ err: thrown }, "Assistant conversation subscription failed on a database error.");
|
|
214
217
|
onStage("failed", { code: String(error?.code || "assistant_subscription_failed"),
|
|
215
218
|
status: errorStatus(error) });
|
|
216
219
|
if (subscriptions.get(subscriptionId) === subscription) release(subscriptionId);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
1
|
+
import { AppError, hideDatabaseError } from "@jskit-ai/kernel/server/runtime";
|
|
2
2
|
import { composeSchemaDefinitions } from "@jskit-ai/kernel/shared/validators";
|
|
3
3
|
import { createJsonApiResourceRouteContract } from "@jskit-ai/http-runtime/shared/validators/jsonApiRouteTransport";
|
|
4
4
|
import {
|
|
5
5
|
ASSISTANT_CONVERSATIONS_TRANSPORT,
|
|
6
6
|
ASSISTANT_CONVERSATION_MESSAGES_TRANSPORT,
|
|
7
7
|
assistantConversationOutputValidator,
|
|
8
|
-
|
|
8
|
+
assistantContract,
|
|
9
9
|
resolveAssistantApiBasePath
|
|
10
10
|
} from "@jskit-ai/assistant-core/shared";
|
|
11
11
|
import {
|
|
@@ -109,7 +109,7 @@ function registerRuntimeRoutes(
|
|
|
109
109
|
? requireWorkspaceAssistantRouteParams(workspaceScopeSupport)
|
|
110
110
|
: assistantSurfaceRouteParamsValidator;
|
|
111
111
|
const conversationMessagesParams = composeSchemaDefinitions(
|
|
112
|
-
[params,
|
|
112
|
+
[params, assistantContract.operations.conversationMessagesList.params],
|
|
113
113
|
{
|
|
114
114
|
mode: "patch",
|
|
115
115
|
context: requiresWorkspace === true
|
|
@@ -129,7 +129,7 @@ function registerRuntimeRoutes(
|
|
|
129
129
|
tags: ["assistant"],
|
|
130
130
|
summary: "Stream assistant response."
|
|
131
131
|
},
|
|
132
|
-
body:
|
|
132
|
+
body: assistantContract.operations.chatStream.body
|
|
133
133
|
},
|
|
134
134
|
async function assistantChatStreamRoute(request, reply) {
|
|
135
135
|
const routeState = resolveRouteRequestState(request, {
|
|
@@ -188,7 +188,10 @@ function registerRuntimeRoutes(
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
reply.code(204).send();
|
|
191
|
-
} catch (
|
|
191
|
+
} catch (thrown) {
|
|
192
|
+
// A database error is answered by its stand-in (a generic 409 or 500), and its detail goes to the log.
|
|
193
|
+
const error = hideDatabaseError(thrown);
|
|
194
|
+
if (error !== thrown) request.log.error({ err: thrown }, "Assistant chat stream failed on a database error.");
|
|
192
195
|
if (!streamStarted) {
|
|
193
196
|
sendPreStreamErrorResponse(reply, error);
|
|
194
197
|
return;
|
|
@@ -223,7 +226,7 @@ function registerRuntimeRoutes(
|
|
|
223
226
|
},
|
|
224
227
|
...createJsonApiResourceRouteContract({
|
|
225
228
|
...ASSISTANT_CONVERSATIONS_TRANSPORT,
|
|
226
|
-
query:
|
|
229
|
+
query: assistantContract.operations.conversationsList.query,
|
|
227
230
|
output: assistantConversationOutputValidator,
|
|
228
231
|
outputKind: "collection"
|
|
229
232
|
})
|
|
@@ -262,8 +265,8 @@ function registerRuntimeRoutes(
|
|
|
262
265
|
},
|
|
263
266
|
...createJsonApiResourceRouteContract({
|
|
264
267
|
...ASSISTANT_CONVERSATION_MESSAGES_TRANSPORT,
|
|
265
|
-
query:
|
|
266
|
-
output:
|
|
268
|
+
query: assistantContract.operations.conversationMessagesList.query,
|
|
269
|
+
output: assistantContract.operations.conversationMessagesList.output,
|
|
267
270
|
outputKind: "record",
|
|
268
271
|
getRecordId: resolveAssistantConversationMessagesRecordId
|
|
269
272
|
})
|
|
@@ -2,7 +2,7 @@ import { createJsonApiResourceRouteContract } from "@jskit-ai/http-runtime/share
|
|
|
2
2
|
import {
|
|
3
3
|
ASSISTANT_SETTINGS_UPDATE_TRANSPORT,
|
|
4
4
|
ASSISTANT_SETTINGS_TRANSPORT,
|
|
5
|
-
|
|
5
|
+
assistantConfigContract,
|
|
6
6
|
resolveAssistantApiBasePath
|
|
7
7
|
} from "@jskit-ai/assistant-core/shared";
|
|
8
8
|
import { actionIds } from "./actionIds.js";
|
|
@@ -45,7 +45,7 @@ function registerSettingsRoutes(
|
|
|
45
45
|
},
|
|
46
46
|
...createJsonApiResourceRouteContract({
|
|
47
47
|
...ASSISTANT_SETTINGS_TRANSPORT,
|
|
48
|
-
output:
|
|
48
|
+
output: assistantConfigContract.operations.view.output,
|
|
49
49
|
outputKind: "record",
|
|
50
50
|
getRecordId: resolveAssistantSettingsRecordId
|
|
51
51
|
})
|
|
@@ -81,8 +81,8 @@ function registerSettingsRoutes(
|
|
|
81
81
|
},
|
|
82
82
|
...createJsonApiResourceRouteContract({
|
|
83
83
|
...ASSISTANT_SETTINGS_UPDATE_TRANSPORT,
|
|
84
|
-
body:
|
|
85
|
-
output:
|
|
84
|
+
body: assistantConfigContract.operations.patch.body,
|
|
85
|
+
output: assistantConfigContract.operations.patch.output,
|
|
86
86
|
outputKind: "record",
|
|
87
87
|
getRecordId: resolveAssistantSettingsRecordId,
|
|
88
88
|
includeValidation400: true
|