@yunsoft/yuncms-api 0.1.20 → 0.1.22
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.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yunsoft/yuncms-api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Express API runtime and bundled Studio server for YunCMS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@modelcontextprotocol/node": "2.0.0",
|
|
36
36
|
"@modelcontextprotocol/server": "2.0.0",
|
|
37
37
|
"@node-saml/node-saml": "5.1.0",
|
|
38
|
-
"@yunsoft/yuncms-core": "0.1.
|
|
38
|
+
"@yunsoft/yuncms-core": "0.1.22",
|
|
39
39
|
"express": "5.2.1",
|
|
40
40
|
"ldapts": "9.0.0",
|
|
41
41
|
"openid-client": "6.8.7",
|
package/src/ai/service.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { canonicalStudioLocale, STUDIO_LOCALE_CODES } from '@yunsoft/yuncms-core';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
aiToolDefinitions,
|
|
3
5
|
executeAiTool,
|
|
@@ -7,7 +9,21 @@ import {
|
|
|
7
9
|
} from './tools.js';
|
|
8
10
|
|
|
9
11
|
const PROVIDER_RESPONSE_LIMIT = 2_000_000;
|
|
10
|
-
const
|
|
12
|
+
export const AI_REPLY_LANGUAGE_BY_LOCALE = Object.freeze({
|
|
13
|
+
en: 'English',
|
|
14
|
+
tr: 'Turkish',
|
|
15
|
+
es: 'Spanish',
|
|
16
|
+
de: 'German',
|
|
17
|
+
fr: 'French',
|
|
18
|
+
'pt-BR': 'Brazilian Portuguese',
|
|
19
|
+
ja: 'Japanese',
|
|
20
|
+
'zh-CN': 'Simplified Chinese',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (Object.keys(AI_REPLY_LANGUAGE_BY_LOCALE).length !== STUDIO_LOCALE_CODES.length
|
|
24
|
+
|| STUDIO_LOCALE_CODES.some((locale) => !AI_REPLY_LANGUAGE_BY_LOCALE[locale])) {
|
|
25
|
+
throw new Error('AI reply languages must cover every supported Studio locale');
|
|
26
|
+
}
|
|
11
27
|
|
|
12
28
|
function aiError(code, message) {
|
|
13
29
|
const error = new Error(message);
|
|
@@ -77,9 +93,10 @@ export function normalizeAiConversation(messages, config) {
|
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
function systemPrompt({ locale, writesEnabled, deletesEnabled }) {
|
|
80
|
-
const
|
|
96
|
+
const normalizedLocale = canonicalStudioLocale(locale) ?? 'en';
|
|
97
|
+
const language = AI_REPLY_LANGUAGE_BY_LOCALE[normalizedLocale];
|
|
81
98
|
return [
|
|
82
|
-
'You are
|
|
99
|
+
'You are the AI assistant embedded inside YunCMS Studio.',
|
|
83
100
|
`Reply in ${language} unless the user explicitly asks for another language.`,
|
|
84
101
|
'Use the provided tools whenever an answer depends on YunCMS schema or records. Never invent collection names, fields, records, counts or permissions.',
|
|
85
102
|
'The tools already enforce the signed-in user\'s YunCMS permissions. If a tool says access is forbidden, explain that the current account does not have that access instead of trying to bypass it.',
|
|
@@ -184,20 +201,20 @@ export class AiAssistantService {
|
|
|
184
201
|
|
|
185
202
|
async chat(req, {
|
|
186
203
|
messages,
|
|
187
|
-
locale = '
|
|
204
|
+
locale = 'en',
|
|
188
205
|
allowWrites = false,
|
|
189
206
|
allowDeletes = false,
|
|
190
207
|
} = {}) {
|
|
191
208
|
const config = await this.settings();
|
|
192
209
|
if (!config.enabled || !config.apiKey || !config.model) {
|
|
193
|
-
throw aiError('AI_NOT_CONFIGURED', '
|
|
210
|
+
throw aiError('AI_NOT_CONFIGURED', 'The AI assistant is not configured on this YunCMS server');
|
|
194
211
|
}
|
|
195
212
|
if (req?.authMethod === 'public' || !req?.accountability?.user) {
|
|
196
|
-
throw aiError('UNAUTHORIZED', '
|
|
213
|
+
throw aiError('UNAUTHORIZED', 'The AI assistant requires an authenticated YunCMS account');
|
|
197
214
|
}
|
|
198
215
|
|
|
199
216
|
const conversation = normalizeAiConversation(messages, config);
|
|
200
|
-
const normalizedLocale =
|
|
217
|
+
const normalizedLocale = canonicalStudioLocale(locale) ?? 'en';
|
|
201
218
|
const writesEnabled = config.writesEnabled === true && allowWrites === true;
|
|
202
219
|
const deletesEnabled = writesEnabled && allowDeletes === true;
|
|
203
220
|
const tools = aiToolDefinitions({ writesEnabled, deletesEnabled, maxItems: 100 });
|
|
@@ -229,7 +246,7 @@ export class AiAssistantService {
|
|
|
229
246
|
}
|
|
230
247
|
|
|
231
248
|
if (round >= config.maxToolRounds) {
|
|
232
|
-
throw aiError('AI_TOOL_ROUND_LIMIT', '
|
|
249
|
+
throw aiError('AI_TOOL_ROUND_LIMIT', 'The AI assistant reached the tool-call safety limit');
|
|
233
250
|
}
|
|
234
251
|
|
|
235
252
|
providerMessages.push(providerAssistantMessage(assistantMessage, toolCalls));
|
|
@@ -261,7 +278,7 @@ export class AiAssistantService {
|
|
|
261
278
|
}
|
|
262
279
|
}
|
|
263
280
|
|
|
264
|
-
throw aiError('AI_TOOL_ROUND_LIMIT', '
|
|
281
|
+
throw aiError('AI_TOOL_ROUND_LIMIT', 'The AI assistant reached the tool-call safety limit');
|
|
265
282
|
}
|
|
266
283
|
}
|
|
267
284
|
|
package/src/ai/settings-store.js
CHANGED
|
@@ -119,10 +119,10 @@ export class AiSettingsStore {
|
|
|
119
119
|
const effectiveModel = Object.hasOwn(normalized, 'model') ? normalized.model : current.model;
|
|
120
120
|
const effectiveEnabled = Object.hasOwn(normalized, 'enabled') ? normalized.enabled : Boolean(current.enabled);
|
|
121
121
|
if (effectiveEnabled && !effectiveHasKey) {
|
|
122
|
-
throw settingsError('INVALID_AI_CONFIG', 'Save an API key before enabling
|
|
122
|
+
throw settingsError('INVALID_AI_CONFIG', 'Save an API key before enabling the AI assistant');
|
|
123
123
|
}
|
|
124
124
|
if (effectiveEnabled && !effectiveModel) {
|
|
125
|
-
throw settingsError('INVALID_AI_CONFIG', 'Choose a model before enabling
|
|
125
|
+
throw settingsError('INVALID_AI_CONFIG', 'Choose a model before enabling the AI assistant');
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
params.push(1);
|
package/src/routes/ai.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
|
|
3
3
|
function authError() {
|
|
4
|
-
const error = new Error('
|
|
4
|
+
const error = new Error('AI assistant requires an authenticated YunCMS account');
|
|
5
5
|
error.code = 'UNAUTHORIZED';
|
|
6
6
|
return error;
|
|
7
7
|
}
|
|
@@ -13,7 +13,7 @@ function requireAuthenticated(req) {
|
|
|
13
13
|
function requireAdministrator(req) {
|
|
14
14
|
requireAuthenticated(req);
|
|
15
15
|
if (req.accountability?.admin === true || req.accountability?.system === true) return;
|
|
16
|
-
const error = new Error('
|
|
16
|
+
const error = new Error('AI assistant settings require administrator access');
|
|
17
17
|
error.code = 'FORBIDDEN';
|
|
18
18
|
throw error;
|
|
19
19
|
}
|