@jskit-ai/assistant-core 0.1.1
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/package.descriptor.mjs +67 -0
- package/package.json +24 -0
- package/src/client/components/AssistantClientElement.vue +1316 -0
- package/src/client/components/AssistantSettingsFormCard.vue +76 -0
- package/src/client/index.js +5 -0
- package/src/client/lib/assistantApi.js +140 -0
- package/src/client/lib/assistantHttpClient.js +10 -0
- package/src/client/lib/markdownRenderer.js +31 -0
- package/src/server/index.js +21 -0
- package/src/server/lib/aiClient.js +43 -0
- package/src/server/lib/ndjson.js +47 -0
- package/src/server/lib/providers/anthropicClient.js +375 -0
- package/src/server/lib/providers/common.js +150 -0
- package/src/server/lib/providers/deepSeekClient.js +22 -0
- package/src/server/lib/providers/openAiClient.js +13 -0
- package/src/server/lib/providers/openAiCompatibleClient.js +69 -0
- package/src/server/lib/resolveWorkspaceSlug.js +24 -0
- package/src/server/lib/serviceToolCatalog.js +459 -0
- package/src/server/repositories/repositoryPersistenceUtils.js +48 -0
- package/src/shared/assistantPaths.js +77 -0
- package/src/shared/assistantResource.js +309 -0
- package/src/shared/assistantSettingsResource.js +90 -0
- package/src/shared/index.js +47 -0
- package/src/shared/queryKeys.js +84 -0
- package/src/shared/settingsEvents.js +5 -0
- package/src/shared/streamEvents.js +29 -0
- package/src/shared/support/conversationStatus.js +18 -0
- package/src/shared/support/jsonObject.js +18 -0
- package/src/shared/support/positiveInteger.js +9 -0
- package/test/aiConfigValidation.test.js +15 -0
- package/test/assistantApiSurfaceHeader.test.js +66 -0
- package/test/assistantPaths.test.js +51 -0
- package/test/assistantResource.test.js +49 -0
- package/test/assistantSettingsResource.test.js +32 -0
- package/test/queryKeys.test.js +44 -0
- package/test/resolveWorkspaceSlug.test.js +83 -0
- package/test/serviceToolCatalog.test.js +1235 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import {
|
|
3
|
+
normalizeObjectInput,
|
|
4
|
+
createCursorListValidator
|
|
5
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
6
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
7
|
+
import { normalizeConversationStatus } from "./support/conversationStatus.js";
|
|
8
|
+
import { toPositiveInteger } from "./support/positiveInteger.js";
|
|
9
|
+
|
|
10
|
+
const MAX_INPUT_CHARS = 8000;
|
|
11
|
+
const MAX_HISTORY_MESSAGES = 20;
|
|
12
|
+
const MAX_PAGE_SIZE = 200;
|
|
13
|
+
const MAX_MESSAGE_PAGE_SIZE = 500;
|
|
14
|
+
|
|
15
|
+
function normalizePaginationValue(value, fallback, max) {
|
|
16
|
+
const parsed = toPositiveInteger(value, fallback);
|
|
17
|
+
return Math.max(1, Math.min(max, parsed));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeChatStreamBody(payload = {}) {
|
|
21
|
+
const source = normalizeObjectInput(payload);
|
|
22
|
+
const normalized = {
|
|
23
|
+
messageId: normalizeText(source.messageId),
|
|
24
|
+
input: normalizeText(source.input)
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const conversationId = toPositiveInteger(source.conversationId, 0);
|
|
28
|
+
if (conversationId > 0) {
|
|
29
|
+
normalized.conversationId = conversationId;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const history = Array.isArray(source.history) ? source.history : [];
|
|
33
|
+
normalized.history = history
|
|
34
|
+
.slice(0, MAX_HISTORY_MESSAGES)
|
|
35
|
+
.map((entry) => {
|
|
36
|
+
const item = normalizeObjectInput(entry);
|
|
37
|
+
const role = normalizeText(item.role).toLowerCase();
|
|
38
|
+
if (role !== "user" && role !== "assistant") {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const content = normalizeText(item.content);
|
|
43
|
+
if (!content) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
role,
|
|
49
|
+
content: content.slice(0, MAX_INPUT_CHARS)
|
|
50
|
+
};
|
|
51
|
+
})
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
|
|
54
|
+
const clientContext = normalizeObjectInput(source.clientContext);
|
|
55
|
+
if (Object.keys(clientContext).length > 0) {
|
|
56
|
+
normalized.clientContext = {
|
|
57
|
+
locale: normalizeText(clientContext.locale),
|
|
58
|
+
timezone: normalizeText(clientContext.timezone)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return normalized;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeConversationsListQuery(payload = {}) {
|
|
66
|
+
const source = normalizeObjectInput(payload);
|
|
67
|
+
const status = normalizeConversationStatus(source.status, {
|
|
68
|
+
fallback: ""
|
|
69
|
+
});
|
|
70
|
+
const normalized = {};
|
|
71
|
+
|
|
72
|
+
if (Object.hasOwn(source, "cursor")) {
|
|
73
|
+
normalized.cursor = toPositiveInteger(source.cursor, 0);
|
|
74
|
+
}
|
|
75
|
+
if (Object.hasOwn(source, "limit")) {
|
|
76
|
+
normalized.limit = toPositiveInteger(source.limit, 0);
|
|
77
|
+
}
|
|
78
|
+
if (status) {
|
|
79
|
+
normalized.status = status;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return normalized;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeConversationMessagesQuery(payload = {}) {
|
|
86
|
+
const source = normalizeObjectInput(payload);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
page: normalizePaginationValue(source.page, 1, MAX_MESSAGE_PAGE_SIZE),
|
|
90
|
+
pageSize: normalizePaginationValue(source.pageSize, 200, MAX_MESSAGE_PAGE_SIZE)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeConversationMessagesParams(payload = {}) {
|
|
95
|
+
const source = normalizeObjectInput(payload);
|
|
96
|
+
return {
|
|
97
|
+
conversationId: toPositiveInteger(source.conversationId, 0)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function createOptionalPositiveIntegerQuerySchema(max = null) {
|
|
102
|
+
const numericSchema = max == null
|
|
103
|
+
? Type.Integer({ minimum: 1 })
|
|
104
|
+
: Type.Integer({ minimum: 1, maximum: max });
|
|
105
|
+
|
|
106
|
+
return Type.Optional(
|
|
107
|
+
Type.Union([
|
|
108
|
+
numericSchema,
|
|
109
|
+
Type.String({ pattern: "^[1-9][0-9]*$" })
|
|
110
|
+
])
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizeConversationRecord(payload = {}) {
|
|
115
|
+
const source = normalizeObjectInput(payload);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
id: toPositiveInteger(source.id, 0),
|
|
119
|
+
workspaceId: toPositiveInteger(source.workspaceId, 0) || null,
|
|
120
|
+
title: normalizeText(source.title),
|
|
121
|
+
createdByUserId: toPositiveInteger(source.createdByUserId, 0) || null,
|
|
122
|
+
status: normalizeText(source.status),
|
|
123
|
+
provider: normalizeText(source.provider),
|
|
124
|
+
model: normalizeText(source.model),
|
|
125
|
+
surfaceId: normalizeText(source.surfaceId || source.surfaceSid).toLowerCase(),
|
|
126
|
+
startedAt: normalizeText(source.startedAt),
|
|
127
|
+
endedAt: normalizeText(source.endedAt) || null,
|
|
128
|
+
messageCount: Math.max(0, Number(source.messageCount || 0)),
|
|
129
|
+
metadata: normalizeObjectInput(source.metadata),
|
|
130
|
+
createdAt: normalizeText(source.createdAt),
|
|
131
|
+
updatedAt: normalizeText(source.updatedAt)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeConversationMessageRecord(payload = {}) {
|
|
136
|
+
const source = normalizeObjectInput(payload);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
id: toPositiveInteger(source.id, 0),
|
|
140
|
+
conversationId: toPositiveInteger(source.conversationId, 0),
|
|
141
|
+
workspaceId: toPositiveInteger(source.workspaceId, 0) || null,
|
|
142
|
+
seq: toPositiveInteger(source.seq, 0),
|
|
143
|
+
role: normalizeText(source.role),
|
|
144
|
+
kind: normalizeText(source.kind),
|
|
145
|
+
clientMessageSid: normalizeText(source.clientMessageSid),
|
|
146
|
+
actorUserId: toPositiveInteger(source.actorUserId, 0) || null,
|
|
147
|
+
contentText: source.contentText == null ? null : String(source.contentText),
|
|
148
|
+
metadata: normalizeObjectInput(source.metadata),
|
|
149
|
+
createdAt: normalizeText(source.createdAt)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const historyMessageSchema = Type.Object(
|
|
154
|
+
{
|
|
155
|
+
role: Type.Union([Type.Literal("user"), Type.Literal("assistant")]),
|
|
156
|
+
content: Type.String({ minLength: 1, maxLength: MAX_INPUT_CHARS })
|
|
157
|
+
},
|
|
158
|
+
{ additionalProperties: false }
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const chatStreamBodySchema = Type.Object(
|
|
162
|
+
{
|
|
163
|
+
messageId: Type.String({ minLength: 1, maxLength: 128 }),
|
|
164
|
+
conversationId: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
165
|
+
input: Type.String({ minLength: 1, maxLength: MAX_INPUT_CHARS }),
|
|
166
|
+
history: Type.Optional(Type.Array(historyMessageSchema, { maxItems: MAX_HISTORY_MESSAGES })),
|
|
167
|
+
clientContext: Type.Optional(
|
|
168
|
+
Type.Object(
|
|
169
|
+
{
|
|
170
|
+
locale: Type.Optional(Type.String({ maxLength: 64 })),
|
|
171
|
+
timezone: Type.Optional(Type.String({ maxLength: 64 }))
|
|
172
|
+
},
|
|
173
|
+
{ additionalProperties: false }
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
additionalProperties: false
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const conversationRecordSchema = Type.Object(
|
|
183
|
+
{
|
|
184
|
+
id: Type.Integer({ minimum: 1 }),
|
|
185
|
+
workspaceId: Type.Union([Type.Integer({ minimum: 1 }), Type.Null()]),
|
|
186
|
+
title: Type.String(),
|
|
187
|
+
createdByUserId: Type.Union([Type.Integer({ minimum: 1 }), Type.Null()]),
|
|
188
|
+
status: Type.String(),
|
|
189
|
+
provider: Type.String(),
|
|
190
|
+
model: Type.String(),
|
|
191
|
+
surfaceId: Type.String(),
|
|
192
|
+
startedAt: Type.String({ minLength: 1 }),
|
|
193
|
+
endedAt: Type.Union([Type.String({ minLength: 1 }), Type.Null()]),
|
|
194
|
+
messageCount: Type.Integer({ minimum: 0 }),
|
|
195
|
+
metadata: Type.Record(Type.String(), Type.Unknown()),
|
|
196
|
+
createdAt: Type.String({ minLength: 1 }),
|
|
197
|
+
updatedAt: Type.String({ minLength: 1 })
|
|
198
|
+
},
|
|
199
|
+
{ additionalProperties: false }
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const conversationRecordValidator = Object.freeze({
|
|
203
|
+
schema: conversationRecordSchema,
|
|
204
|
+
normalize: normalizeConversationRecord
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const messageRecordSchema = Type.Object(
|
|
208
|
+
{
|
|
209
|
+
id: Type.Integer({ minimum: 1 }),
|
|
210
|
+
conversationId: Type.Integer({ minimum: 1 }),
|
|
211
|
+
workspaceId: Type.Union([Type.Integer({ minimum: 1 }), Type.Null()]),
|
|
212
|
+
seq: Type.Integer({ minimum: 1 }),
|
|
213
|
+
role: Type.String({ minLength: 1 }),
|
|
214
|
+
kind: Type.String({ minLength: 1 }),
|
|
215
|
+
clientMessageSid: Type.String(),
|
|
216
|
+
actorUserId: Type.Union([Type.Integer({ minimum: 1 }), Type.Null()]),
|
|
217
|
+
contentText: Type.Union([Type.String(), Type.Null()]),
|
|
218
|
+
metadata: Type.Record(Type.String(), Type.Unknown()),
|
|
219
|
+
createdAt: Type.String({ minLength: 1 })
|
|
220
|
+
},
|
|
221
|
+
{ additionalProperties: false }
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const paginationProperties = Object.freeze({
|
|
225
|
+
page: Type.Integer({ minimum: 1 }),
|
|
226
|
+
pageSize: Type.Integer({ minimum: 1 }),
|
|
227
|
+
total: Type.Integer({ minimum: 0 }),
|
|
228
|
+
totalPages: Type.Integer({ minimum: 1 })
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const assistantResource = Object.freeze({
|
|
232
|
+
resource: "assistant",
|
|
233
|
+
operations: {
|
|
234
|
+
chatStream: {
|
|
235
|
+
method: "POST",
|
|
236
|
+
bodyValidator: Object.freeze({
|
|
237
|
+
schema: chatStreamBodySchema,
|
|
238
|
+
normalize: normalizeChatStreamBody
|
|
239
|
+
})
|
|
240
|
+
},
|
|
241
|
+
conversationsList: {
|
|
242
|
+
method: "GET",
|
|
243
|
+
queryValidator: Object.freeze({
|
|
244
|
+
schema: Type.Object(
|
|
245
|
+
{
|
|
246
|
+
cursor: createOptionalPositiveIntegerQuerySchema(),
|
|
247
|
+
limit: createOptionalPositiveIntegerQuerySchema(MAX_PAGE_SIZE),
|
|
248
|
+
status: Type.Optional(Type.String({ minLength: 1, maxLength: 32 }))
|
|
249
|
+
},
|
|
250
|
+
{ additionalProperties: false }
|
|
251
|
+
),
|
|
252
|
+
normalize: normalizeConversationsListQuery
|
|
253
|
+
}),
|
|
254
|
+
outputValidator: createCursorListValidator(conversationRecordValidator)
|
|
255
|
+
},
|
|
256
|
+
conversationMessagesList: {
|
|
257
|
+
method: "GET",
|
|
258
|
+
paramsValidator: Object.freeze({
|
|
259
|
+
schema: Type.Object(
|
|
260
|
+
{
|
|
261
|
+
conversationId: Type.Union([
|
|
262
|
+
Type.Integer({ minimum: 1 }),
|
|
263
|
+
Type.String({ pattern: "^[1-9][0-9]*$" })
|
|
264
|
+
])
|
|
265
|
+
},
|
|
266
|
+
{ additionalProperties: false }
|
|
267
|
+
),
|
|
268
|
+
normalize: normalizeConversationMessagesParams
|
|
269
|
+
}),
|
|
270
|
+
queryValidator: Object.freeze({
|
|
271
|
+
schema: Type.Object(
|
|
272
|
+
{
|
|
273
|
+
page: createOptionalPositiveIntegerQuerySchema(),
|
|
274
|
+
pageSize: createOptionalPositiveIntegerQuerySchema(MAX_MESSAGE_PAGE_SIZE)
|
|
275
|
+
},
|
|
276
|
+
{ additionalProperties: false }
|
|
277
|
+
),
|
|
278
|
+
normalize: normalizeConversationMessagesQuery
|
|
279
|
+
}),
|
|
280
|
+
outputValidator: Object.freeze({
|
|
281
|
+
schema: Type.Object(
|
|
282
|
+
{
|
|
283
|
+
...paginationProperties,
|
|
284
|
+
conversation: conversationRecordSchema,
|
|
285
|
+
entries: Type.Array(messageRecordSchema)
|
|
286
|
+
},
|
|
287
|
+
{ additionalProperties: false }
|
|
288
|
+
),
|
|
289
|
+
normalize(payload = {}) {
|
|
290
|
+
const source = normalizeObjectInput(payload);
|
|
291
|
+
return {
|
|
292
|
+
conversation: normalizeConversationRecord(source.conversation),
|
|
293
|
+
entries: (Array.isArray(source.entries) ? source.entries : []).map(normalizeConversationMessageRecord),
|
|
294
|
+
page: normalizePaginationValue(source.page, 1, MAX_MESSAGE_PAGE_SIZE),
|
|
295
|
+
pageSize: normalizePaginationValue(source.pageSize, 200, MAX_MESSAGE_PAGE_SIZE),
|
|
296
|
+
total: Math.max(0, Number(source.total || 0)),
|
|
297
|
+
totalPages: Math.max(1, Number(source.totalPages || 1))
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
export {
|
|
306
|
+
MAX_INPUT_CHARS,
|
|
307
|
+
MAX_HISTORY_MESSAGES,
|
|
308
|
+
assistantResource
|
|
309
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators";
|
|
4
|
+
import { toPositiveInteger } from "./support/positiveInteger.js";
|
|
5
|
+
|
|
6
|
+
const MAX_SYSTEM_PROMPT_CHARS = 12_000;
|
|
7
|
+
|
|
8
|
+
const assistantConfigRecordSchema = Type.Object(
|
|
9
|
+
{
|
|
10
|
+
targetSurfaceId: Type.String({ minLength: 1, maxLength: 64 }),
|
|
11
|
+
scopeKey: Type.String({ minLength: 1, maxLength: 160 }),
|
|
12
|
+
workspaceId: Type.Union([Type.Integer({ minimum: 1 }), Type.Null()]),
|
|
13
|
+
settings: Type.Object(
|
|
14
|
+
{
|
|
15
|
+
systemPrompt: Type.String({ maxLength: MAX_SYSTEM_PROMPT_CHARS })
|
|
16
|
+
},
|
|
17
|
+
{ additionalProperties: false }
|
|
18
|
+
)
|
|
19
|
+
},
|
|
20
|
+
{ additionalProperties: false }
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const assistantConfigPatchSchema = Type.Object(
|
|
24
|
+
{
|
|
25
|
+
systemPrompt: Type.Optional(
|
|
26
|
+
Type.String({
|
|
27
|
+
maxLength: MAX_SYSTEM_PROMPT_CHARS,
|
|
28
|
+
messages: {
|
|
29
|
+
maxLength: `Assistant system prompt must be at most ${MAX_SYSTEM_PROMPT_CHARS} characters.`,
|
|
30
|
+
default: "Assistant system prompt must be valid text."
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
)
|
|
34
|
+
},
|
|
35
|
+
{ additionalProperties: false }
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
function normalizeConfigPatch(payload = {}) {
|
|
39
|
+
const source = normalizeObjectInput(payload);
|
|
40
|
+
const normalized = {};
|
|
41
|
+
|
|
42
|
+
if (Object.hasOwn(source, "systemPrompt")) {
|
|
43
|
+
normalized.systemPrompt = String(source.systemPrompt || "");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeConfigRecord(payload = {}) {
|
|
50
|
+
const source = normalizeObjectInput(payload);
|
|
51
|
+
const settings = normalizeObjectInput(source.settings);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
targetSurfaceId: normalizeText(source.targetSurfaceId).toLowerCase(),
|
|
55
|
+
scopeKey: normalizeText(source.scopeKey),
|
|
56
|
+
workspaceId: toPositiveInteger(source.workspaceId, 0) || null,
|
|
57
|
+
settings: {
|
|
58
|
+
systemPrompt: String(settings.systemPrompt || "")
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const assistantConfigResource = Object.freeze({
|
|
64
|
+
resource: "assistantConfig",
|
|
65
|
+
operations: Object.freeze({
|
|
66
|
+
view: Object.freeze({
|
|
67
|
+
method: "GET",
|
|
68
|
+
outputValidator: Object.freeze({
|
|
69
|
+
schema: assistantConfigRecordSchema,
|
|
70
|
+
normalize: normalizeConfigRecord
|
|
71
|
+
})
|
|
72
|
+
}),
|
|
73
|
+
patch: Object.freeze({
|
|
74
|
+
method: "PATCH",
|
|
75
|
+
bodyValidator: Object.freeze({
|
|
76
|
+
schema: assistantConfigPatchSchema,
|
|
77
|
+
normalize: normalizeConfigPatch
|
|
78
|
+
}),
|
|
79
|
+
outputValidator: Object.freeze({
|
|
80
|
+
schema: assistantConfigRecordSchema,
|
|
81
|
+
normalize: normalizeConfigRecord
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
MAX_SYSTEM_PROMPT_CHARS,
|
|
89
|
+
assistantConfigResource
|
|
90
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ASSISTANT_API_RELATIVE_PATH,
|
|
3
|
+
ASSISTANT_SETTINGS_API_RELATIVE_PATH,
|
|
4
|
+
ASSISTANT_WORKSPACE_API_BASE_PATH_TEMPLATE,
|
|
5
|
+
ASSISTANT_PUBLIC_API_BASE_PATH,
|
|
6
|
+
ASSISTANT_WORKSPACE_SETTINGS_API_PATH_TEMPLATE,
|
|
7
|
+
ASSISTANT_PUBLIC_SETTINGS_API_PATH,
|
|
8
|
+
resolveAssistantApiBasePath,
|
|
9
|
+
resolveAssistantSettingsApiPath,
|
|
10
|
+
buildAssistantApiPath,
|
|
11
|
+
buildAssistantSettingsApiPath
|
|
12
|
+
} from "./assistantPaths.js";
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
ASSISTANT_QUERY_KEY_PREFIX,
|
|
16
|
+
assistantRootQueryKey,
|
|
17
|
+
assistantScopeQueryKey,
|
|
18
|
+
assistantSettingsQueryKey,
|
|
19
|
+
assistantConversationsListQueryKey,
|
|
20
|
+
assistantConversationMessagesQueryKey
|
|
21
|
+
} from "./queryKeys.js";
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
ASSISTANT_STREAM_EVENT_TYPES,
|
|
25
|
+
normalizeAssistantStreamEventType
|
|
26
|
+
} from "./streamEvents.js";
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
MAX_INPUT_CHARS,
|
|
30
|
+
MAX_HISTORY_MESSAGES,
|
|
31
|
+
assistantResource
|
|
32
|
+
} from "./assistantResource.js";
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
MAX_SYSTEM_PROMPT_CHARS,
|
|
36
|
+
assistantConfigResource
|
|
37
|
+
} from "./assistantSettingsResource.js";
|
|
38
|
+
|
|
39
|
+
export { assistantSettingsEvents } from "./settingsEvents.js";
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
ASSISTANT_CONVERSATION_STATUSES,
|
|
43
|
+
normalizeConversationStatus
|
|
44
|
+
} from "./support/conversationStatus.js";
|
|
45
|
+
|
|
46
|
+
export { parseJsonObject } from "./support/jsonObject.js";
|
|
47
|
+
export { toPositiveInteger } from "./support/positiveInteger.js";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const ASSISTANT_QUERY_KEY_PREFIX = Object.freeze(["assistant"]);
|
|
2
|
+
|
|
3
|
+
function normalizeSurfaceId(value) {
|
|
4
|
+
return String(value || "").trim().toLowerCase() || "none";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeWorkspaceSlug(value) {
|
|
8
|
+
return String(value || "").trim() || "none";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizePositiveInteger(value, fallback) {
|
|
12
|
+
const parsed = Number(value);
|
|
13
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeScopeKey({ targetSurfaceId = "", workspaceSlug = "", workspaceId = 0 } = {}) {
|
|
21
|
+
const normalizedWorkspaceId = normalizePositiveInteger(workspaceId, 0);
|
|
22
|
+
const normalizedSurfaceId = normalizeSurfaceId(targetSurfaceId);
|
|
23
|
+
if (normalizedWorkspaceId > 0) {
|
|
24
|
+
return `${normalizedSurfaceId}:workspace:${normalizedWorkspaceId}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const normalizedWorkspaceSlug = normalizeWorkspaceSlug(workspaceSlug);
|
|
28
|
+
if (normalizedWorkspaceSlug !== "none") {
|
|
29
|
+
return `${normalizedSurfaceId}:slug:${normalizedWorkspaceSlug}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `${normalizedSurfaceId}:global`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeStatus(value) {
|
|
36
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
37
|
+
return normalized || "all";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeConversationId(value) {
|
|
41
|
+
return String(normalizePositiveInteger(value, 0) || "none");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function assistantRootQueryKey() {
|
|
45
|
+
return [...ASSISTANT_QUERY_KEY_PREFIX];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function assistantScopeQueryKey(scope = {}) {
|
|
49
|
+
return [...assistantRootQueryKey(), normalizeScopeKey(scope)];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function assistantSettingsQueryKey(scope = {}) {
|
|
53
|
+
return [...assistantScopeQueryKey(scope), "settings"];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function assistantConversationsListQueryKey(scope = {}, { limit = 20, status = "" } = {}) {
|
|
57
|
+
return [
|
|
58
|
+
...assistantScopeQueryKey(scope),
|
|
59
|
+
"conversations",
|
|
60
|
+
"list",
|
|
61
|
+
normalizePositiveInteger(limit, 20),
|
|
62
|
+
normalizeStatus(status)
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function assistantConversationMessagesQueryKey(scope = {}, conversationId, { page = 1, pageSize = 200 } = {}) {
|
|
67
|
+
return [
|
|
68
|
+
...assistantScopeQueryKey(scope),
|
|
69
|
+
"conversations",
|
|
70
|
+
normalizeConversationId(conversationId),
|
|
71
|
+
"messages",
|
|
72
|
+
normalizePositiveInteger(page, 1),
|
|
73
|
+
normalizePositiveInteger(pageSize, 200)
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
ASSISTANT_QUERY_KEY_PREFIX,
|
|
79
|
+
assistantRootQueryKey,
|
|
80
|
+
assistantScopeQueryKey,
|
|
81
|
+
assistantSettingsQueryKey,
|
|
82
|
+
assistantConversationsListQueryKey,
|
|
83
|
+
assistantConversationMessagesQueryKey
|
|
84
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const ASSISTANT_STREAM_EVENT_TYPES = Object.freeze({
|
|
2
|
+
META: "meta",
|
|
3
|
+
ASSISTANT_DELTA: "assistant_delta",
|
|
4
|
+
ASSISTANT_MESSAGE: "assistant_message",
|
|
5
|
+
TOOL_CALL: "tool_call",
|
|
6
|
+
TOOL_RESULT: "tool_result",
|
|
7
|
+
ERROR: "error",
|
|
8
|
+
DONE: "done"
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const STREAM_EVENT_TYPE_SET = new Set(Object.values(ASSISTANT_STREAM_EVENT_TYPES));
|
|
12
|
+
|
|
13
|
+
function normalizeAssistantStreamEventType(value, fallback = "") {
|
|
14
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
15
|
+
if (!normalized) {
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!STREAM_EVENT_TYPE_SET.has(normalized)) {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
ASSISTANT_STREAM_EVENT_TYPES,
|
|
28
|
+
normalizeAssistantStreamEventType
|
|
29
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
|
|
3
|
+
const ASSISTANT_CONVERSATION_STATUSES = Object.freeze(["active", "completed", "failed", "aborted"]);
|
|
4
|
+
const ASSISTANT_CONVERSATION_STATUS_SET = new Set(ASSISTANT_CONVERSATION_STATUSES);
|
|
5
|
+
|
|
6
|
+
function normalizeConversationStatus(value, { fallback = "" } = {}) {
|
|
7
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
8
|
+
if (ASSISTANT_CONVERSATION_STATUS_SET.has(normalized)) {
|
|
9
|
+
return normalized;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return normalizeText(fallback).toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
ASSISTANT_CONVERSATION_STATUSES,
|
|
17
|
+
normalizeConversationStatus
|
|
18
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function parseJsonObject(value) {
|
|
2
|
+
if (value == null) {
|
|
3
|
+
return {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
8
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return parsed;
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { parseJsonObject };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { normalizeOptionalHttpUrl } from "../src/server/lib/providers/common.js";
|
|
4
|
+
|
|
5
|
+
test("normalizeOptionalHttpUrl accepts empty values", () => {
|
|
6
|
+
assert.equal(normalizeOptionalHttpUrl(""), "");
|
|
7
|
+
assert.equal(normalizeOptionalHttpUrl(" "), "");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("normalizeOptionalHttpUrl rejects non-http absolute values", () => {
|
|
11
|
+
assert.throws(
|
|
12
|
+
() => normalizeOptionalHttpUrl("cd ../89", { context: "assistant AI_BASE_URL" }),
|
|
13
|
+
/assistant AI_BASE_URL must be an absolute http\(s\) URL\./
|
|
14
|
+
);
|
|
15
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createAssistantApi } from "../src/client/lib/assistantApi.js";
|
|
4
|
+
|
|
5
|
+
test("assistant api forwards normalized surface header on requests", async () => {
|
|
6
|
+
const observed = {
|
|
7
|
+
stream: null,
|
|
8
|
+
list: null,
|
|
9
|
+
messages: null
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const api = createAssistantApi({
|
|
13
|
+
resolveBasePath: () => "/api/assistant",
|
|
14
|
+
resolveSurfaceId: () => "AdMiN",
|
|
15
|
+
async request(url, options = {}) {
|
|
16
|
+
if (url.includes("/messages")) {
|
|
17
|
+
observed.messages = { url, options };
|
|
18
|
+
} else {
|
|
19
|
+
observed.list = { url, options };
|
|
20
|
+
}
|
|
21
|
+
return {};
|
|
22
|
+
},
|
|
23
|
+
async requestStream(url, options = {}) {
|
|
24
|
+
observed.stream = { url, options };
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await api.streamChat({
|
|
30
|
+
messageId: "msg_1",
|
|
31
|
+
input: "Hello"
|
|
32
|
+
});
|
|
33
|
+
await api.listConversations({
|
|
34
|
+
limit: 5
|
|
35
|
+
});
|
|
36
|
+
await api.getConversationMessages(99, {
|
|
37
|
+
page: 1,
|
|
38
|
+
pageSize: 5
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
assert.equal(observed.stream?.options?.headers?.["x-jskit-surface"], "admin");
|
|
42
|
+
assert.equal(observed.list?.options?.headers?.["x-jskit-surface"], "admin");
|
|
43
|
+
assert.equal(observed.messages?.options?.headers?.["x-jskit-surface"], "admin");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("assistant api omits surface header when surface id is empty", async () => {
|
|
47
|
+
const observed = [];
|
|
48
|
+
const api = createAssistantApi({
|
|
49
|
+
resolveBasePath: () => "/api/assistant",
|
|
50
|
+
resolveSurfaceId: () => "",
|
|
51
|
+
async request(url, options = {}) {
|
|
52
|
+
observed.push({
|
|
53
|
+
url,
|
|
54
|
+
options
|
|
55
|
+
});
|
|
56
|
+
return {};
|
|
57
|
+
},
|
|
58
|
+
async requestStream(_url, _options = {}) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await api.listConversations();
|
|
64
|
+
assert.equal(observed.length, 1);
|
|
65
|
+
assert.equal(Object.hasOwn(observed[0].options || {}, "headers"), false);
|
|
66
|
+
});
|