@jskit-ai/assistant-runtime 0.1.234 → 0.1.236
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 +36 -0
- package/fixtures/conversation/vite.config.mjs +2 -2
- package/package.json +9 -10
- package/src/client/components/AssistantSettingsClientElement.vue +0 -3
- package/src/client/components/AssistantSurfaceClientElement.vue +2 -5
- package/src/client/composables/useAssistantConversation.js +57 -55
- package/src/client/composables/useAssistantRuntime.js +14 -29
- package/src/client/support/accessDenied.js +6 -0
- package/src/client/support/subscribeAssistantConversation.js +4 -5
- package/src/server/actions.js +3 -36
- package/src/server/createAssistantSettingsActions.js +0 -8
- package/src/server/inputSchemas.js +3 -8
- package/src/server/registerConversationSubscriptions.js +13 -15
- package/src/server/registerRoutes.js +15 -39
- package/src/server/repositories/conversationsRepository.js +9 -47
- package/src/server/repositories/messagesRepository.js +5 -22
- package/src/server/repositories/repositoryPersistenceUtils.js +34 -1
- package/src/server/services/assistantConfigService.js +4 -17
- package/src/server/services/chatService.js +14 -37
- package/src/server/services/transcriptService.js +1 -54
- package/src/server/support/assistantErrors.js +13 -0
- package/src/server/support/assistantRouteContext.js +1 -10
- package/src/server/support/assistantStreamWriter.js +12 -0
- package/src/server/support/assistantSurface.js +13 -0
- package/src/server/support/createSurfaceAwareToolCatalog.js +5 -6
- package/src/shared/assistantRuntimeConfig.js +3 -1
- package/src/shared/assistantSurfaces.js +18 -0
- package/test/actionDefinitions.test.js +0 -1
- package/test/assistantSurfaces.test.js +29 -0
- package/test/chatServiceLifecycle.test.js +12 -3
- package/test/turnRequests.test.js +0 -1
|
@@ -46,10 +46,6 @@ function createAssistantSettingsActions({ assistantConfigService, settingsSurfac
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
idempotency: "none",
|
|
49
|
-
audit: {
|
|
50
|
-
actionName: actionIds.settingsRead
|
|
51
|
-
},
|
|
52
|
-
observability: {},
|
|
53
49
|
async execute(input, context) {
|
|
54
50
|
return returnJsonApiData(await assistantConfigService.getSettings(input, {
|
|
55
51
|
context
|
|
@@ -73,10 +69,6 @@ function createAssistantSettingsActions({ assistantConfigService, settingsSurfac
|
|
|
73
69
|
}
|
|
74
70
|
},
|
|
75
71
|
idempotency: "optional",
|
|
76
|
-
audit: {
|
|
77
|
-
actionName: actionIds.settingsUpdate
|
|
78
|
-
},
|
|
79
|
-
observability: {},
|
|
80
72
|
async execute(input, context) {
|
|
81
73
|
return returnJsonApiData(await assistantConfigService.updateSettings(input, input.patch, {
|
|
82
74
|
context
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
3
3
|
import { composeSchemaDefinitions } from "@jskit-ai/kernel/shared/validators";
|
|
4
|
+
import { attachmentIdsField } from "@jskit-ai/assistant-core/shared";
|
|
4
5
|
|
|
5
6
|
const assistantSurfaceRouteParamsSchema = createSchema({
|
|
6
7
|
surfaceId: {
|
|
@@ -65,10 +66,7 @@ const assistantConversationSendBodyValidator = deepFreeze({
|
|
|
65
66
|
schema: createSchema({
|
|
66
67
|
messageId: { type: "string", required: true, minLength: 1, maxLength: 128 },
|
|
67
68
|
text: { type: "string", required: true },
|
|
68
|
-
attachmentIds:
|
|
69
|
-
type: "array", required: false, maxItems: 10,
|
|
70
|
-
items: { type: "string", minLength: 1, maxLength: 256 }
|
|
71
|
-
},
|
|
69
|
+
attachmentIds: attachmentIdsField,
|
|
72
70
|
steer: { type: "boolean", required: false }
|
|
73
71
|
}),
|
|
74
72
|
mode: "patch"
|
|
@@ -111,10 +109,7 @@ const assistantConversationGoalBodyValidator = deepFreeze({
|
|
|
111
109
|
messageId: { type: "string", required: false, minLength: 1, maxLength: 128 },
|
|
112
110
|
objective: { type: "string", required: false, minLength: 1, maxLength: 4000 },
|
|
113
111
|
tokenBudget: { type: "integer", required: false, nullable: true, min: 1, max: Number.MAX_SAFE_INTEGER },
|
|
114
|
-
attachmentIds:
|
|
115
|
-
type: "array", required: false, maxItems: 10,
|
|
116
|
-
items: { type: "string", minLength: 1, maxLength: 256 }
|
|
117
|
-
}
|
|
112
|
+
attachmentIds: attachmentIdsField
|
|
118
113
|
}),
|
|
119
114
|
mode: "replace"
|
|
120
115
|
});
|
|
@@ -3,7 +3,8 @@ import { attachDirectRequestActionExecutor } from "@jskit-ai/kernel/server/http"
|
|
|
3
3
|
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
4
4
|
import { actionIds } from "./actionIds.js";
|
|
5
5
|
import { resolveRouteRequestState } from "./support/assistantRouteContext.js";
|
|
6
|
-
import {
|
|
6
|
+
import { errorStatus } from "./support/assistantErrors.js";
|
|
7
|
+
import { requireAssistantSurface } from "./support/assistantSurface.js";
|
|
7
8
|
import {
|
|
8
9
|
ASSISTANT_CONVERSATION_EVENT,
|
|
9
10
|
ASSISTANT_CONVERSATION_SUBSCRIBE,
|
|
@@ -18,7 +19,7 @@ function requireSubscriptionId(input) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function subscriptionError(error) {
|
|
21
|
-
const status =
|
|
22
|
+
const status = errorStatus(error);
|
|
22
23
|
return {
|
|
23
24
|
ok: false,
|
|
24
25
|
error: status < 500 ? String(error.message || "Conversation subscription failed.") : "Conversation subscription failed.",
|
|
@@ -69,14 +70,16 @@ function registerConversationSubscriptions({ realtime, events, actions, config =
|
|
|
69
70
|
let stage = "request-admission";
|
|
70
71
|
let observedConversationId;
|
|
71
72
|
let diagnosticTimer;
|
|
73
|
+
function stageRecord(stageName, fields, now) {
|
|
74
|
+
return { event: "assistant.conversation.subscription", subscriptionEpoch: subscription?.epoch,
|
|
75
|
+
...(typeof observedConversationId === "string" ? { conversationId: observedConversationId } : {}),
|
|
76
|
+
stage: stageName, previousStage: stage, durationMs: now - stageStartedAt, elapsedMs: now - startedAt, ...fields };
|
|
77
|
+
}
|
|
72
78
|
function diagnostic(stageName, fields = {}) {
|
|
73
79
|
if (!diagnostics) return;
|
|
74
80
|
const now = performance.now();
|
|
75
81
|
try {
|
|
76
|
-
logger?.warn?.(
|
|
77
|
-
...(typeof observedConversationId === "string" ? { conversationId: observedConversationId } : {}),
|
|
78
|
-
stage: stageName, previousStage: stage, durationMs: now - stageStartedAt,
|
|
79
|
-
elapsedMs: now - startedAt, ...fields }, "Assistant conversation subscription diagnostic.");
|
|
82
|
+
logger?.warn?.(stageRecord(stageName, fields, now), "Assistant conversation subscription diagnostic.");
|
|
80
83
|
} catch { /* Diagnostics never change admission or cleanup. */ }
|
|
81
84
|
}
|
|
82
85
|
function onStage(next, fields = {}) {
|
|
@@ -86,11 +89,7 @@ function registerConversationSubscriptions({ realtime, events, actions, config =
|
|
|
86
89
|
try {
|
|
87
90
|
const slowOutcome = now - startedAt >= 5_000 && ["acknowledgement", "abandoned"].includes(next);
|
|
88
91
|
const level = diagnostics || next === "failed" || slowOutcome ? "warn" : "info";
|
|
89
|
-
logger?.[level]?.(
|
|
90
|
-
...(typeof observedConversationId === "string" ? { conversationId: observedConversationId } : {}),
|
|
91
|
-
stage: next, previousStage: stage,
|
|
92
|
-
durationMs: now - stageStartedAt, elapsedMs: now - startedAt, ...fields },
|
|
93
|
-
"Assistant conversation subscription stage");
|
|
92
|
+
logger?.[level]?.(stageRecord(next, fields, now), "Assistant conversation subscription stage");
|
|
94
93
|
} catch { /* Diagnostics must not change conversation admission or cleanup. */ }
|
|
95
94
|
stage = next;
|
|
96
95
|
stageStartedAt = now;
|
|
@@ -113,8 +112,7 @@ function registerConversationSubscriptions({ realtime, events, actions, config =
|
|
|
113
112
|
const request = await (requestPolicy === "host" ? readRequest() : authenticate());
|
|
114
113
|
if (!current()) { onStage("abandoned"); return; }
|
|
115
114
|
onStage("action-admission");
|
|
116
|
-
const assistantSurface =
|
|
117
|
-
if (!assistantSurface) throw new AppError(404, "Assistant not found.");
|
|
115
|
+
const assistantSurface = requireAssistantSurface(config, input.targetSurfaceId);
|
|
118
116
|
const requiresWorkspace = assistantSurface.runtimeSurfaceRequiresWorkspace;
|
|
119
117
|
request.headers = { ...request.headers, "x-jskit-surface": input.hostSurfaceId };
|
|
120
118
|
request.input = { params: {
|
|
@@ -155,7 +153,7 @@ function registerConversationSubscriptions({ realtime, events, actions, config =
|
|
|
155
153
|
function onEvent(event) {
|
|
156
154
|
if (!current()) return;
|
|
157
155
|
const revision = ++subscription.revision;
|
|
158
|
-
//
|
|
156
|
+
// Conversation runtimes publish full-text in-progress replies; only the latest per message needs delivery.
|
|
159
157
|
if (event.type === "message" && event.status === "inProgress") {
|
|
160
158
|
subscription.pending.set(event.messageId, { event, revision });
|
|
161
159
|
subscription.timer ||= setTimeout(flush, 25);
|
|
@@ -187,7 +185,7 @@ function registerConversationSubscriptions({ realtime, events, actions, config =
|
|
|
187
185
|
onStage("acknowledgement-sent");
|
|
188
186
|
} catch (error) {
|
|
189
187
|
onStage("failed", { code: String(error?.code || "assistant_subscription_failed"),
|
|
190
|
-
status:
|
|
188
|
+
status: errorStatus(error) });
|
|
191
189
|
if (subscriptions.get(subscriptionId) === subscription) release(subscriptionId);
|
|
192
190
|
if (!closed && socket.connected) acknowledge(subscriptionError(error));
|
|
193
191
|
} finally {
|
|
@@ -27,14 +27,12 @@ import {
|
|
|
27
27
|
assistantSurfaceRouteParamsValidator
|
|
28
28
|
} from "./inputSchemas.js";
|
|
29
29
|
import { registerSettingsRoutes } from "./registerSettingsRoutes.js";
|
|
30
|
+
import { errorStatus, exposedAppErrorDetails } from "./support/assistantErrors.js";
|
|
31
|
+
import { ASSISTANT_STREAM_WRITER_METHODS } from "./support/assistantStreamWriter.js";
|
|
30
32
|
import { requireWorkspaceAssistantRouteParams, resolveRouteRequestState } from "./support/assistantRouteContext.js";
|
|
31
33
|
|
|
32
|
-
function shouldExposeAppErrorDetails(errorCode = "") {
|
|
33
|
-
return String(errorCode || "").trim() !== "ACTION_PERMISSION_DENIED";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
34
|
function sendPreStreamErrorResponse(reply, error) {
|
|
37
|
-
const statusCode =
|
|
35
|
+
const statusCode = errorStatus(error);
|
|
38
36
|
const safeStatusCode = Number.isInteger(statusCode) && statusCode >= 400 && statusCode <= 599 ? statusCode : 500;
|
|
39
37
|
|
|
40
38
|
if (error instanceof AppError) {
|
|
@@ -44,10 +42,11 @@ function sendPreStreamErrorResponse(reply, error) {
|
|
|
44
42
|
code: appErrorCode
|
|
45
43
|
};
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
const details = exposedAppErrorDetails(error);
|
|
46
|
+
if (details) {
|
|
47
|
+
payload.details = details;
|
|
48
|
+
if (details.fieldErrors) {
|
|
49
|
+
payload.fieldErrors = details.fieldErrors;
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
|
|
@@ -156,36 +155,13 @@ function registerRuntimeRoutes(
|
|
|
156
155
|
streamStarted = true;
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
writeNdjson(reply, payload);
|
|
167
|
-
},
|
|
168
|
-
sendAssistantMessage(payload = {}) {
|
|
169
|
-
ensureStreamStarted();
|
|
170
|
-
writeNdjson(reply, payload);
|
|
171
|
-
},
|
|
172
|
-
sendToolCall(payload = {}) {
|
|
173
|
-
ensureStreamStarted();
|
|
174
|
-
writeNdjson(reply, payload);
|
|
175
|
-
},
|
|
176
|
-
sendToolResult(payload = {}) {
|
|
177
|
-
ensureStreamStarted();
|
|
178
|
-
writeNdjson(reply, payload);
|
|
179
|
-
},
|
|
180
|
-
sendError(payload = {}) {
|
|
181
|
-
ensureStreamStarted();
|
|
182
|
-
writeNdjson(reply, payload);
|
|
183
|
-
},
|
|
184
|
-
sendDone(payload = {}) {
|
|
185
|
-
ensureStreamStarted();
|
|
186
|
-
writeNdjson(reply, payload);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
158
|
+
function sendStreamEvent(payload = {}) {
|
|
159
|
+
ensureStreamStarted();
|
|
160
|
+
writeNdjson(reply, payload);
|
|
161
|
+
}
|
|
162
|
+
const streamWriter = Object.freeze(
|
|
163
|
+
Object.fromEntries(ASSISTANT_STREAM_WRITER_METHODS.map(name => [name, sendStreamEvent]))
|
|
164
|
+
);
|
|
189
165
|
|
|
190
166
|
try {
|
|
191
167
|
request.raw.on("close", closeListener);
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import { applyForUpdate, createWithTransaction, normalizeDbRecordId, runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
2
2
|
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
4
|
import {
|
|
5
|
+
applyWorkspaceScope,
|
|
6
|
+
normalizeDbWorkspaceId,
|
|
7
|
+
normalizeInputRecordId,
|
|
8
|
+
normalizeWorkspaceId,
|
|
5
9
|
parseJsonObject,
|
|
6
10
|
resolveInsertedId,
|
|
7
11
|
stringifyJsonObject,
|
|
8
12
|
toIso
|
|
9
13
|
} from "./repositoryPersistenceUtils.js";
|
|
10
|
-
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
11
|
-
|
|
12
|
-
function normalizeWorkspaceId(value) {
|
|
13
|
-
return normalizeRecordId(value, { fallback: null });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function normalizeDbWorkspaceId(value) {
|
|
17
|
-
return normalizeDbRecordId(value, { fallback: null });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function normalizeInputRecordId(value) {
|
|
21
|
-
return normalizeRecordId(value, { fallback: null });
|
|
22
|
-
}
|
|
14
|
+
import { DEFAULT_CONVERSATION_TITLE, assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
23
15
|
|
|
24
16
|
function normalizeRequiredSurfaceId(value) {
|
|
25
17
|
const normalizedSurfaceId = normalizeSurfaceId(value);
|
|
@@ -30,20 +22,11 @@ function normalizeRequiredSurfaceId(value) {
|
|
|
30
22
|
return normalizedSurfaceId;
|
|
31
23
|
}
|
|
32
24
|
|
|
33
|
-
function applyWorkspaceScope(query, columnName, workspaceId) {
|
|
34
|
-
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
|
|
35
|
-
if (normalizedWorkspaceId) {
|
|
36
|
-
return query.where(columnName, normalizedWorkspaceId);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return query.whereNull(columnName);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
25
|
function mapConversationRow(row = {}) {
|
|
43
26
|
return {
|
|
44
27
|
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
45
28
|
workspaceId: normalizeDbWorkspaceId(row.workspace_id),
|
|
46
|
-
title: String(row.title ||
|
|
29
|
+
title: String(row.title || DEFAULT_CONVERSATION_TITLE),
|
|
47
30
|
createdByUserId: row.created_by_user_id == null ? null : normalizeDbRecordId(row.created_by_user_id, { fallback: null }),
|
|
48
31
|
status: String(row.status || "active"),
|
|
49
32
|
provider: String(row.provider || ""),
|
|
@@ -132,7 +115,7 @@ function createRepository(knex) {
|
|
|
132
115
|
const insertResult = await client(assistantRuntimeConfig.conversationsTable).insert({
|
|
133
116
|
workspace_id: normalizeWorkspaceId(payload.workspaceId),
|
|
134
117
|
created_by_user_id: normalizeInputRecordId(payload.createdByUserId),
|
|
135
|
-
title: normalizeText(payload.title) ||
|
|
118
|
+
title: normalizeText(payload.title) || DEFAULT_CONVERSATION_TITLE,
|
|
136
119
|
status: normalizeText(payload.status).toLowerCase() || "active",
|
|
137
120
|
provider: normalizeText(payload.provider),
|
|
138
121
|
model: normalizeText(payload.model),
|
|
@@ -164,7 +147,7 @@ function createRepository(knex) {
|
|
|
164
147
|
const updatePatch = {};
|
|
165
148
|
|
|
166
149
|
if (Object.hasOwn(patch, "title")) {
|
|
167
|
-
updatePatch.title = normalizeText(patch.title) ||
|
|
150
|
+
updatePatch.title = normalizeText(patch.title) || DEFAULT_CONVERSATION_TITLE;
|
|
168
151
|
}
|
|
169
152
|
if (Object.hasOwn(patch, "status")) {
|
|
170
153
|
updatePatch.status = normalizeText(patch.status).toLowerCase() || "active";
|
|
@@ -200,26 +183,6 @@ function createRepository(knex) {
|
|
|
200
183
|
});
|
|
201
184
|
}
|
|
202
185
|
|
|
203
|
-
async function incrementMessageCount(conversationId, delta = 1, options = {}) {
|
|
204
|
-
const normalizedConversationId = normalizeInputRecordId(conversationId);
|
|
205
|
-
if (!normalizedConversationId) {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const client = options?.trx || knex;
|
|
210
|
-
const incrementBy = Number.isInteger(Number(delta)) ? Number(delta) : 1;
|
|
211
|
-
await client(assistantRuntimeConfig.conversationsTable)
|
|
212
|
-
.where({ id: normalizedConversationId })
|
|
213
|
-
.update({
|
|
214
|
-
message_count: client.raw("GREATEST(0, message_count + ?)", [incrementBy]),
|
|
215
|
-
updated_at: new Date()
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
return findById(normalizedConversationId, {
|
|
219
|
-
trx: client
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
186
|
async function listForActorScope(
|
|
224
187
|
{ workspaceId = null, actorUserId = null, surfaceId = "", pagination = {}, filters = {} } = {},
|
|
225
188
|
options = {}
|
|
@@ -275,7 +238,6 @@ function createRepository(knex) {
|
|
|
275
238
|
findByIdForActorScope,
|
|
276
239
|
create,
|
|
277
240
|
updateById,
|
|
278
|
-
incrementMessageCount,
|
|
279
241
|
listForActorScope,
|
|
280
242
|
transaction
|
|
281
243
|
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createWithTransaction, normalizeDbRecordId, runInTransaction } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
3
|
import {
|
|
4
|
+
applyWorkspaceScope,
|
|
5
|
+
normalizeDbWorkspaceId,
|
|
6
|
+
normalizeInputRecordId,
|
|
7
|
+
normalizeWorkspaceId,
|
|
4
8
|
parseJsonObject,
|
|
5
9
|
resolveInsertedId,
|
|
6
10
|
stringifyJsonObject,
|
|
@@ -8,27 +12,6 @@ import {
|
|
|
8
12
|
} from "./repositoryPersistenceUtils.js";
|
|
9
13
|
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
10
14
|
|
|
11
|
-
function normalizeWorkspaceId(value) {
|
|
12
|
-
return normalizeRecordId(value, { fallback: null });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function normalizeDbWorkspaceId(value) {
|
|
16
|
-
return normalizeDbRecordId(value, { fallback: null });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function normalizeInputRecordId(value) {
|
|
20
|
-
return normalizeRecordId(value, { fallback: null });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function applyWorkspaceScope(query, columnName, workspaceId) {
|
|
24
|
-
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
|
|
25
|
-
if (normalizedWorkspaceId) {
|
|
26
|
-
return query.where(columnName, normalizedWorkspaceId);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return query.whereNull(columnName);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
15
|
function mapMessageRow(row = {}) {
|
|
33
16
|
return {
|
|
34
17
|
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { resolveInsertedRecordId } from "@jskit-ai/database-runtime/shared";
|
|
2
|
+
import { normalizeDbRecordId } from "@jskit-ai/database-runtime/shared/repositoryOptions";
|
|
3
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
4
|
import { parseJsonObject } from "@jskit-ai/assistant-core/shared";
|
|
3
5
|
|
|
6
|
+
function normalizeWorkspaceId(value) {
|
|
7
|
+
return normalizeRecordId(value, { fallback: null });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizeDbWorkspaceId(value) {
|
|
11
|
+
return normalizeDbRecordId(value, { fallback: null });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeInputRecordId(value) {
|
|
15
|
+
return normalizeRecordId(value, { fallback: null });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function applyWorkspaceScope(query, columnName, workspaceId) {
|
|
19
|
+
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
|
|
20
|
+
if (normalizedWorkspaceId) {
|
|
21
|
+
return query.where(columnName, normalizedWorkspaceId);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return query.whereNull(columnName);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
4
28
|
function stringifyJsonObject(value) {
|
|
5
29
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
6
30
|
return null;
|
|
@@ -26,4 +50,13 @@ function resolveInsertedId(insertResult) {
|
|
|
26
50
|
return resolveInsertedRecordId(insertResult, { fallback: "" }) || "";
|
|
27
51
|
}
|
|
28
52
|
|
|
29
|
-
export {
|
|
53
|
+
export {
|
|
54
|
+
applyWorkspaceScope,
|
|
55
|
+
normalizeDbWorkspaceId,
|
|
56
|
+
normalizeInputRecordId,
|
|
57
|
+
normalizeWorkspaceId,
|
|
58
|
+
parseJsonObject,
|
|
59
|
+
stringifyJsonObject,
|
|
60
|
+
toIso,
|
|
61
|
+
resolveInsertedId
|
|
62
|
+
};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { AppError, requireAuth } from "@jskit-ai/kernel/server/runtime";
|
|
2
2
|
import { normalizeObject, normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
-
import {
|
|
3
|
+
import { requireAssistantSurface } from "../support/assistantSurface.js";
|
|
4
4
|
|
|
5
5
|
function createService({
|
|
6
6
|
assistantConfigRepository,
|
|
7
7
|
consoleService = null,
|
|
8
8
|
appConfig = {},
|
|
9
|
-
resolveAppConfig = null,
|
|
10
9
|
workspaceScopeSupport = null
|
|
11
10
|
} = {}) {
|
|
12
11
|
if (!assistantConfigRepository || typeof assistantConfigRepository.findByScope !== "function") {
|
|
@@ -19,18 +18,6 @@ function createService({
|
|
|
19
18
|
throw new Error("assistantConfigService requires assistantConfigRepository.createDefaultRecord().");
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
const resolveCurrentAppConfig =
|
|
23
|
-
typeof resolveAppConfig === "function" ? resolveAppConfig : () => appConfig;
|
|
24
|
-
|
|
25
|
-
function requireAssistantSurface(targetSurfaceId = "") {
|
|
26
|
-
const assistantSurface = resolveAssistantSurfaceConfig(resolveCurrentAppConfig(), targetSurfaceId);
|
|
27
|
-
if (assistantSurface) {
|
|
28
|
-
return assistantSurface;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
throw new AppError(404, "Assistant not found.");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
async function requireSettingsAccess(assistantSurface, context = {}, { mode = "read" } = {}) {
|
|
35
22
|
requireAuth({ context }, {
|
|
36
23
|
require: "authenticated"
|
|
@@ -82,7 +69,7 @@ function createService({
|
|
|
82
69
|
}
|
|
83
70
|
|
|
84
71
|
async function getSettings(input = {}, options = {}) {
|
|
85
|
-
const assistantSurface = requireAssistantSurface(input?.targetSurfaceId);
|
|
72
|
+
const assistantSurface = requireAssistantSurface(appConfig, input?.targetSurfaceId);
|
|
86
73
|
await requireSettingsAccess(assistantSurface, options?.context, {
|
|
87
74
|
mode: "read"
|
|
88
75
|
});
|
|
@@ -100,7 +87,7 @@ function createService({
|
|
|
100
87
|
}
|
|
101
88
|
|
|
102
89
|
async function updateSettings(input = {}, patch = {}, options = {}) {
|
|
103
|
-
const assistantSurface = requireAssistantSurface(input?.targetSurfaceId);
|
|
90
|
+
const assistantSurface = requireAssistantSurface(appConfig, input?.targetSurfaceId);
|
|
104
91
|
await requireSettingsAccess(assistantSurface, options?.context, {
|
|
105
92
|
mode: "write"
|
|
106
93
|
});
|
|
@@ -116,7 +103,7 @@ function createService({
|
|
|
116
103
|
}
|
|
117
104
|
|
|
118
105
|
async function resolveSystemPrompt(assistantSurface, workspace = null, _options = {}, serviceOptions = {}) {
|
|
119
|
-
const resolvedAssistantSurface = requireAssistantSurface(assistantSurface?.targetSurfaceId);
|
|
106
|
+
const resolvedAssistantSurface = requireAssistantSurface(appConfig, assistantSurface?.targetSurfaceId);
|
|
120
107
|
const workspaceId = resolveConfigWorkspaceId(
|
|
121
108
|
resolvedAssistantSurface,
|
|
122
109
|
workspace,
|
|
@@ -2,11 +2,12 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
2
2
|
import { AppError } from "@jskit-ai/kernel/server/runtime";
|
|
3
3
|
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
4
|
import { resolveWorkspaceSlug } from "@jskit-ai/assistant-core/server";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { conversationAttachmentIds } from "@jskit-ai/assistant-core/server/conversation";
|
|
6
|
+
import { ASSISTANT_STREAM_EVENT_TYPES, MAX_HISTORY_MESSAGES, MAX_INPUT_CHARS, isAssistantProgressOnlyText } from "@jskit-ai/assistant-core/shared";
|
|
7
|
+
import { exposedAppErrorDetails, errorStatus } from "../support/assistantErrors.js";
|
|
8
|
+
import { requireAssistantSurface } from "../support/assistantSurface.js";
|
|
9
|
+
import { ASSISTANT_STREAM_WRITER_METHODS } from "../support/assistantStreamWriter.js";
|
|
7
10
|
|
|
8
|
-
const MAX_HISTORY_MESSAGES = 20;
|
|
9
|
-
const MAX_INPUT_CHARS = 8000;
|
|
10
11
|
const CLOCK_INSTRUCTION = "For current or relative date and time questions, first use any available authoritative workspace clock action; never infer the current date or time from model knowledge.";
|
|
11
12
|
|
|
12
13
|
function normalizeConversationId(value) {
|
|
@@ -71,16 +72,7 @@ function normalizeStreamInput(payload = {}) {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
function hasStreamWriter(streamWriter) {
|
|
74
|
-
return
|
|
75
|
-
streamWriter &&
|
|
76
|
-
typeof streamWriter.sendMeta === "function" &&
|
|
77
|
-
typeof streamWriter.sendAssistantDelta === "function" &&
|
|
78
|
-
typeof streamWriter.sendAssistantMessage === "function" &&
|
|
79
|
-
typeof streamWriter.sendToolCall === "function" &&
|
|
80
|
-
typeof streamWriter.sendToolResult === "function" &&
|
|
81
|
-
typeof streamWriter.sendError === "function" &&
|
|
82
|
-
typeof streamWriter.sendDone === "function"
|
|
83
|
-
);
|
|
75
|
+
return ASSISTANT_STREAM_WRITER_METHODS.every(name => typeof streamWriter?.[name] === "function");
|
|
84
76
|
}
|
|
85
77
|
|
|
86
78
|
function isAbortError(error) {
|
|
@@ -157,15 +149,6 @@ function buildSystemPrompt({ targetSurfaceId = "", toolDescriptors = [], workspa
|
|
|
157
149
|
return promptSegments.join(" ");
|
|
158
150
|
}
|
|
159
151
|
|
|
160
|
-
function requireAssistantSurface(appConfig = {}, targetSurfaceId = "") {
|
|
161
|
-
const assistantSurface = resolveAssistantSurfaceConfig(appConfig, targetSurfaceId);
|
|
162
|
-
if (assistantSurface) {
|
|
163
|
-
return assistantSurface;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
throw new AppError(404, "Assistant not found.");
|
|
167
|
-
}
|
|
168
|
-
|
|
169
152
|
function buildAssistantActionContext(context = {}, assistantSurface = {}) {
|
|
170
153
|
return {
|
|
171
154
|
...normalizeObject(context),
|
|
@@ -182,7 +165,6 @@ function createChatService({
|
|
|
182
165
|
serviceToolCatalog,
|
|
183
166
|
assistantConfigService,
|
|
184
167
|
appConfig = {},
|
|
185
|
-
resolveAppConfig = null,
|
|
186
168
|
workspaceScopeSupport = null
|
|
187
169
|
} = {}) {
|
|
188
170
|
if (!aiClientFactory || typeof aiClientFactory.resolveClient !== "function" || !turnRequests || !transcriptService || !serviceToolCatalog || !assistantConfigService) {
|
|
@@ -191,9 +173,6 @@ function createChatService({
|
|
|
191
173
|
);
|
|
192
174
|
}
|
|
193
175
|
|
|
194
|
-
const resolveCurrentAppConfig =
|
|
195
|
-
typeof resolveAppConfig === "function" ? resolveAppConfig : () => appConfig;
|
|
196
|
-
|
|
197
176
|
function resolveRuntimeWorkspace(assistantSurface, context = {}, input = {}) {
|
|
198
177
|
if (assistantSurface?.runtimeSurfaceRequiresWorkspace !== true) {
|
|
199
178
|
return null;
|
|
@@ -230,7 +209,7 @@ function createChatService({
|
|
|
230
209
|
}
|
|
231
210
|
|
|
232
211
|
async function streamChat(payload = {}, options = {}) {
|
|
233
|
-
const assistantSurface = requireAssistantSurface(
|
|
212
|
+
const assistantSurface = requireAssistantSurface(appConfig, payload?.targetSurfaceId);
|
|
234
213
|
const context = normalizeObject(options.context);
|
|
235
214
|
const assistantContext = buildAssistantActionContext(context, assistantSurface);
|
|
236
215
|
const workspace = resolveRuntimeWorkspace(assistantSurface, assistantContext, payload);
|
|
@@ -270,9 +249,9 @@ function createChatService({
|
|
|
270
249
|
result = await executeTurn();
|
|
271
250
|
} catch (error) {
|
|
272
251
|
response.failure = { message: String(error?.message || "Assistant request failed."),
|
|
273
|
-
status:
|
|
252
|
+
status: errorStatus(error),
|
|
274
253
|
...(error instanceof AppError ? { code: error.code,
|
|
275
|
-
...(
|
|
254
|
+
...(exposedAppErrorDetails(error) ? { details: error.details } : {}) } : {}) };
|
|
276
255
|
await turnRequests.update(claim, response, "failed");
|
|
277
256
|
throw error;
|
|
278
257
|
}
|
|
@@ -307,10 +286,8 @@ function createChatService({
|
|
|
307
286
|
const resolvedAttachments = [];
|
|
308
287
|
async function prepareAttachments(attachmentIds) {
|
|
309
288
|
if (attachmentIds === undefined || (Array.isArray(attachmentIds) && !attachmentIds.length)) return;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
throw new AppError(400, "Invalid attachment identifiers.");
|
|
313
|
-
}
|
|
289
|
+
try { conversationAttachmentIds(attachmentIds); }
|
|
290
|
+
catch { throw new AppError(400, "Invalid attachment identifiers."); }
|
|
314
291
|
if (!attachments?.resolve || !aiClient.supportsAttachments) {
|
|
315
292
|
throw new AppError(400, "File attachments are not configured for this assistant.");
|
|
316
293
|
}
|
|
@@ -402,7 +379,7 @@ function createChatService({
|
|
|
402
379
|
streamWriter.sendError({ type: ASSISTANT_STREAM_EVENT_TYPES.ERROR, messageId: source.messageId,
|
|
403
380
|
code: aborted ? "assistant_stream_aborted" : String(error?.code || "assistant_stream_failed"),
|
|
404
381
|
message: aborted ? "Assistant request was cancelled." : String(error?.message || "Assistant request failed."),
|
|
405
|
-
status: aborted ? 499 :
|
|
382
|
+
status: aborted ? 499 : errorStatus(error) });
|
|
406
383
|
streamWriter.sendDone({ type: ASSISTANT_STREAM_EVENT_TYPES.DONE, messageId: source.messageId, status });
|
|
407
384
|
return { conversationId, messageId: source.messageId, status };
|
|
408
385
|
} finally {
|
|
@@ -414,7 +391,7 @@ function createChatService({
|
|
|
414
391
|
}
|
|
415
392
|
|
|
416
393
|
async function listConversations(query = {}, options = {}) {
|
|
417
|
-
const assistantSurface = requireAssistantSurface(
|
|
394
|
+
const assistantSurface = requireAssistantSurface(appConfig, options?.input?.targetSurfaceId);
|
|
418
395
|
const context = normalizeObject(options.context);
|
|
419
396
|
const assistantContext = buildAssistantActionContext(context, assistantSurface);
|
|
420
397
|
const workspace = resolveRuntimeWorkspace(assistantSurface, assistantContext, options.input || {});
|
|
@@ -424,7 +401,7 @@ function createChatService({
|
|
|
424
401
|
}
|
|
425
402
|
|
|
426
403
|
async function getConversationMessages(conversationId, query = {}, options = {}) {
|
|
427
|
-
const assistantSurface = requireAssistantSurface(
|
|
404
|
+
const assistantSurface = requireAssistantSurface(appConfig, options?.input?.targetSurfaceId);
|
|
428
405
|
const context = normalizeObject(options.context);
|
|
429
406
|
const assistantContext = buildAssistantActionContext(context, assistantSurface);
|
|
430
407
|
const workspace = resolveRuntimeWorkspace(assistantSurface, assistantContext, options.input || {});
|