@rei-standard/amsg-server 2.0.0-pre1 → 2.0.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/README.md +13 -2
- package/dist/index.cjs +63 -14
- package/dist/index.d.cts +120 -24
- package/dist/index.d.ts +120 -24
- package/dist/index.mjs +59 -10
- package/dist/{neon-DyBfYFkz.d.ts → neon-CJl66EGy.d.cts} +2 -2
- package/dist/{neon-Ch9Abtfp.d.cts → neon-DYvGnCzx.d.ts} +2 -2
- package/dist/{pg-DJWS_HZ3.d.ts → pg-07S-u_H4.d.cts} +2 -2
- package/dist/{pg-BpYu3O81.d.cts → pg-Du-pN_UT.d.ts} +2 -2
- package/dist/{schema-D-ldTuIC.d.cts → schema-C8OnYk6j.d.cts} +1 -1
- package/dist/{schema-D-ldTuIC.d.ts → schema-C8OnYk6j.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @rei-standard/amsg-server
|
|
2
2
|
|
|
3
|
-
`@rei-standard/amsg-server` 是 ReiStandard 主动消息标准的服务端 SDK(v2.0.
|
|
3
|
+
`@rei-standard/amsg-server` 是 ReiStandard 主动消息标准的服务端 SDK(v2.0.1),提供 Blob 租户配置、租户 token 鉴权和标准路由处理器。
|
|
4
4
|
|
|
5
|
-
## v2.0.
|
|
5
|
+
## v2.0.1 变更摘要
|
|
6
6
|
|
|
7
7
|
- 初始化流程合并为 `POST /api/v1/init-tenant`
|
|
8
8
|
- 移除旧端点:`init-database`、`init-master-key`
|
|
@@ -50,6 +50,17 @@ const rei = await createReiServer({
|
|
|
50
50
|
// GET /api/v1/messages -> rei.handlers.messages.GET
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
## AI 接口 `apiUrl` 约束
|
|
54
|
+
|
|
55
|
+
当 `messageType` 为 `prompted` / `auto`,或 `instant` 使用 AI 配置时:
|
|
56
|
+
|
|
57
|
+
- `apiUrl` 必须是完整聊天端点(例如:`https://api.openai.com/v1/chat/completions`)。
|
|
58
|
+
- SDK 会自动做最小规范化:去首尾空白、去路径尾部多余 `/`。
|
|
59
|
+
- SDK **不会**自动补全 `/v1`、`/chat/completions` 等路径。
|
|
60
|
+
- `maxTokens` 为可选字段:传了就映射为 `max_tokens`;不传则不指定(由上游模型默认策略决定)。
|
|
61
|
+
|
|
62
|
+
如果上游返回 `405 Method Not Allowed`,通常表示 `apiUrl` 指向了基础域名而非聊天端点,请优先检查配置值。
|
|
63
|
+
|
|
53
64
|
## 一体化初始化流程
|
|
54
65
|
|
|
55
66
|
1. 管理员配置环境变量(VAPID + tenant secrets)
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/server/adapters/factory.js
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/server/adapters/factory.js
|
|
2
2
|
async function createAdapter(config) {
|
|
3
3
|
if (!config || !config.driver) {
|
|
4
4
|
throw new Error(
|
|
@@ -66,6 +66,9 @@ function validateScheduleMessagePayload(payload) {
|
|
|
66
66
|
if (payload.recurrenceType && !["none", "daily", "weekly"].includes(payload.recurrenceType)) {
|
|
67
67
|
return { valid: false, errorCode: "INVALID_PARAMETERS", errorMessage: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\u6216\u53C2\u6570\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["recurrenceType"] } };
|
|
68
68
|
}
|
|
69
|
+
if (payload.maxTokens !== void 0 && payload.maxTokens !== null && (!Number.isInteger(payload.maxTokens) || payload.maxTokens <= 0)) {
|
|
70
|
+
return { valid: false, errorCode: "INVALID_PARAMETERS", errorMessage: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\u6216\u53C2\u6570\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["maxTokens"] } };
|
|
71
|
+
}
|
|
69
72
|
if (payload.messageType === "fixed") {
|
|
70
73
|
if (!payload.userMessage) {
|
|
71
74
|
return { valid: false, errorCode: "INVALID_PARAMETERS", errorMessage: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\u6216\u53C2\u6570\u683C\u5F0F\u9519\u8BEF", details: { missingFields: ["userMessage (required for fixed type)"] } };
|
|
@@ -505,25 +508,66 @@ async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, provided
|
|
|
505
508
|
}
|
|
506
509
|
}
|
|
507
510
|
async function _callAI(payload) {
|
|
508
|
-
const
|
|
511
|
+
const normalizedApiUrl = normalizeAiApiUrl(payload.apiUrl);
|
|
512
|
+
const requestBody = buildAiRequestBody(payload);
|
|
513
|
+
const aiResponse = await fetch(normalizedApiUrl, {
|
|
509
514
|
method: "POST",
|
|
510
515
|
headers: {
|
|
511
516
|
"Content-Type": "application/json",
|
|
512
517
|
"Authorization": `Bearer ${payload.apiKey}`
|
|
513
518
|
},
|
|
514
|
-
body: JSON.stringify(
|
|
515
|
-
model: payload.primaryModel,
|
|
516
|
-
messages: [{ role: "user", content: payload.completePrompt }],
|
|
517
|
-
max_tokens: 500,
|
|
518
|
-
temperature: 0.8
|
|
519
|
-
}),
|
|
519
|
+
body: JSON.stringify(requestBody),
|
|
520
520
|
signal: AbortSignal.timeout(3e5)
|
|
521
521
|
});
|
|
522
522
|
if (!aiResponse.ok) {
|
|
523
|
-
|
|
523
|
+
if (aiResponse.status === 405) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`AI API error: 405 Method Not Allowed. apiUrl must point to a full chat endpoint (for example: /chat/completions). Received: ${normalizedApiUrl}`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
throw new Error(
|
|
529
|
+
`AI API error: ${aiResponse.status} ${aiResponse.statusText || "Unknown Error"}. Request URL: ${normalizedApiUrl}`
|
|
530
|
+
);
|
|
524
531
|
}
|
|
525
532
|
const aiData = await aiResponse.json();
|
|
526
|
-
|
|
533
|
+
const content = _optionalChain([aiData, 'optionalAccess', _ => _.choices, 'optionalAccess', _2 => _2[0], 'optionalAccess', _3 => _3.message, 'optionalAccess', _4 => _4.content]);
|
|
534
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
535
|
+
throw new Error("AI API error: response missing choices[0].message.content");
|
|
536
|
+
}
|
|
537
|
+
return content.trim();
|
|
538
|
+
}
|
|
539
|
+
function buildAiRequestBody(payload) {
|
|
540
|
+
const requestBody = {
|
|
541
|
+
model: payload.primaryModel,
|
|
542
|
+
messages: [{ role: "user", content: payload.completePrompt }],
|
|
543
|
+
temperature: 0.8
|
|
544
|
+
};
|
|
545
|
+
if (payload.maxTokens === void 0 || payload.maxTokens === null) {
|
|
546
|
+
return requestBody;
|
|
547
|
+
}
|
|
548
|
+
if (!Number.isInteger(payload.maxTokens) || payload.maxTokens <= 0) {
|
|
549
|
+
throw new Error("Invalid maxTokens: maxTokens must be a positive integer when provided.");
|
|
550
|
+
}
|
|
551
|
+
requestBody.max_tokens = payload.maxTokens;
|
|
552
|
+
return requestBody;
|
|
553
|
+
}
|
|
554
|
+
function normalizeAiApiUrl(apiUrl) {
|
|
555
|
+
if (typeof apiUrl !== "string" || !apiUrl.trim()) {
|
|
556
|
+
throw new Error(
|
|
557
|
+
"Invalid apiUrl: apiUrl is required. Please provide a full chat endpoint URL (for example: https://api.openai.com/v1/chat/completions)."
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const trimmedApiUrl = apiUrl.trim();
|
|
561
|
+
let parsedUrl;
|
|
562
|
+
try {
|
|
563
|
+
parsedUrl = new URL(trimmedApiUrl);
|
|
564
|
+
} catch (e3) {
|
|
565
|
+
throw new Error(
|
|
566
|
+
`Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL that points to a full chat endpoint.`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, "") || "/";
|
|
570
|
+
return parsedUrl.toString();
|
|
527
571
|
}
|
|
528
572
|
|
|
529
573
|
// src/server/handlers/schedule-message.js
|
|
@@ -591,6 +635,7 @@ function createScheduleMessageHandler(ctx) {
|
|
|
591
635
|
apiKey: payload.apiKey || null,
|
|
592
636
|
primaryModel: payload.primaryModel || null,
|
|
593
637
|
completePrompt: payload.completePrompt || null,
|
|
638
|
+
maxTokens: _nullishCoalesce(payload.maxTokens, () => ( null)),
|
|
594
639
|
pushSubscription: payload.pushSubscription,
|
|
595
640
|
metadata: payload.metadata || {}
|
|
596
641
|
};
|
|
@@ -899,6 +944,9 @@ function createUpdateMessageHandler(ctx) {
|
|
|
899
944
|
if (updates.recurrenceType && !["none", "daily", "weekly"].includes(updates.recurrenceType)) {
|
|
900
945
|
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["recurrenceType"] } } } };
|
|
901
946
|
}
|
|
947
|
+
if (Object.prototype.hasOwnProperty.call(updates, "maxTokens") && updates.maxTokens !== null && (!Number.isInteger(updates.maxTokens) || updates.maxTokens <= 0)) {
|
|
948
|
+
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["maxTokens"] } } } };
|
|
949
|
+
}
|
|
902
950
|
const existingTask = await db.getTaskByUuid(taskUuid, userId);
|
|
903
951
|
if (!existingTask) {
|
|
904
952
|
const taskStatus = await db.getTaskStatus(taskUuid, userId);
|
|
@@ -914,7 +962,8 @@ function createUpdateMessageHandler(ctx) {
|
|
|
914
962
|
...updates.userMessage && { userMessage: updates.userMessage },
|
|
915
963
|
...updates.recurrenceType && { recurrenceType: updates.recurrenceType },
|
|
916
964
|
...updates.avatarUrl && { avatarUrl: updates.avatarUrl },
|
|
917
|
-
...updates.metadata && { metadata: updates.metadata }
|
|
965
|
+
...updates.metadata && { metadata: updates.metadata },
|
|
966
|
+
...Object.prototype.hasOwnProperty.call(updates, "maxTokens") && { maxTokens: _nullishCoalesce(updates.maxTokens, () => ( null)) }
|
|
918
967
|
};
|
|
919
968
|
const encryptedPayload = encryptForStorage(JSON.stringify(updatedData), userKey);
|
|
920
969
|
const extraFields = updates.nextSendAt ? { next_send_at: updates.nextSendAt } : void 0;
|
|
@@ -1106,7 +1155,7 @@ async function resolveBlobStore(namespace) {
|
|
|
1106
1155
|
}
|
|
1107
1156
|
try {
|
|
1108
1157
|
const blobModule = await Promise.resolve().then(() => _interopRequireWildcard(require("@netlify/blobs")));
|
|
1109
|
-
const getStore = blobModule.getStore || _optionalChain([blobModule, 'access',
|
|
1158
|
+
const getStore = blobModule.getStore || _optionalChain([blobModule, 'access', _5 => _5.default, 'optionalAccess', _6 => _6.getStore]);
|
|
1110
1159
|
if (typeof getStore === "function") {
|
|
1111
1160
|
return getStore({ name: namespace });
|
|
1112
1161
|
}
|
|
@@ -1194,7 +1243,7 @@ function verifyTenantToken(token, secret, options = {}) {
|
|
|
1194
1243
|
let payload;
|
|
1195
1244
|
try {
|
|
1196
1245
|
payload = JSON.parse(base64UrlDecode2(encodedPayload).toString("utf8"));
|
|
1197
|
-
} catch (
|
|
1246
|
+
} catch (e4) {
|
|
1198
1247
|
throw new Error("INVALID_TENANT_AUTH");
|
|
1199
1248
|
}
|
|
1200
1249
|
if (!payload || typeof payload !== "object" || payload.v !== 1) {
|
|
@@ -1251,7 +1300,7 @@ function parseTokenFromUrl(url) {
|
|
|
1251
1300
|
try {
|
|
1252
1301
|
const parsed = new URL(url, "https://dummy.local");
|
|
1253
1302
|
return String(parsed.searchParams.get("token") || "").trim();
|
|
1254
|
-
} catch (
|
|
1303
|
+
} catch (e5) {
|
|
1255
1304
|
return "";
|
|
1256
1305
|
}
|
|
1257
1306
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ import { createDecipheriv, createHash, randomBytes, createCipheriv, randomUUID,
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adapter Factory
|
|
5
|
-
* ReiStandard SDK v2.0.
|
|
5
|
+
* ReiStandard SDK v2.0.1
|
|
6
6
|
*
|
|
7
7
|
* Creates a database adapter instance based on the supplied configuration.
|
|
8
8
|
*
|
|
@@ -35,12 +35,12 @@ async function createAdapter(config) {
|
|
|
35
35
|
|
|
36
36
|
switch (config.driver) {
|
|
37
37
|
case 'neon': {
|
|
38
|
-
const { NeonAdapter } = await import('./neon-
|
|
38
|
+
const { NeonAdapter } = await import('./neon-CJl66EGy.d.cts');
|
|
39
39
|
return new NeonAdapter(config.connectionString);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
case 'pg': {
|
|
43
|
-
const { PgAdapter } = await import('./pg-
|
|
43
|
+
const { PgAdapter } = await import('./pg-07S-u_H4.d.cts');
|
|
44
44
|
return new PgAdapter(config.connectionString);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -54,7 +54,7 @@ async function createAdapter(config) {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Validation utility library (SDK version)
|
|
57
|
-
* ReiStandard SDK v2.0.
|
|
57
|
+
* ReiStandard SDK v2.0.1
|
|
58
58
|
*/
|
|
59
59
|
|
|
60
60
|
/**
|
|
@@ -132,6 +132,14 @@ function validateScheduleMessagePayload(payload) {
|
|
|
132
132
|
return { valid: false, errorCode: 'INVALID_PARAMETERS', errorMessage: '缺少必需参数或参数格式错误', details: { invalidFields: ['recurrenceType'] } };
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
if (
|
|
136
|
+
payload.maxTokens !== undefined &&
|
|
137
|
+
payload.maxTokens !== null &&
|
|
138
|
+
(!Number.isInteger(payload.maxTokens) || payload.maxTokens <= 0)
|
|
139
|
+
) {
|
|
140
|
+
return { valid: false, errorCode: 'INVALID_PARAMETERS', errorMessage: '缺少必需参数或参数格式错误', details: { invalidFields: ['maxTokens'] } };
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
if (payload.messageType === 'fixed') {
|
|
136
144
|
if (!payload.userMessage) {
|
|
137
145
|
return { valid: false, errorCode: 'INVALID_PARAMETERS', errorMessage: '缺少必需参数或参数格式错误', details: { missingFields: ['userMessage (required for fixed type)'] } };
|
|
@@ -320,7 +328,7 @@ function getHeader(headers = {}, name) {
|
|
|
320
328
|
|
|
321
329
|
/**
|
|
322
330
|
* Handler: init-tenant
|
|
323
|
-
* ReiStandard SDK v2.0.
|
|
331
|
+
* ReiStandard SDK v2.0.1
|
|
324
332
|
*
|
|
325
333
|
* @param {Object} ctx - Server context.
|
|
326
334
|
* @returns {{ POST: function }}
|
|
@@ -456,7 +464,7 @@ function createInitTenantHandler(ctx) {
|
|
|
456
464
|
|
|
457
465
|
/**
|
|
458
466
|
* Encryption utility library (SDK version)
|
|
459
|
-
* ReiStandard SDK v2.0.
|
|
467
|
+
* ReiStandard SDK v2.0.1
|
|
460
468
|
*
|
|
461
469
|
* Wraps AES-256-GCM operations for request/response and storage encryption.
|
|
462
470
|
*/
|
|
@@ -558,7 +566,7 @@ function decryptFromStorage(encryptedText, encryptionKey) {
|
|
|
558
566
|
|
|
559
567
|
/**
|
|
560
568
|
* Handler: get-user-key
|
|
561
|
-
* ReiStandard SDK v2.0.
|
|
569
|
+
* ReiStandard SDK v2.0.1
|
|
562
570
|
*
|
|
563
571
|
* @param {Object} ctx - Server context.
|
|
564
572
|
* @returns {{ GET: function }}
|
|
@@ -627,7 +635,7 @@ function isUniqueViolation(error) {
|
|
|
627
635
|
|
|
628
636
|
/**
|
|
629
637
|
* Message Processor (SDK version)
|
|
630
|
-
* ReiStandard SDK v2.0.
|
|
638
|
+
* ReiStandard SDK v2.0.1
|
|
631
639
|
*
|
|
632
640
|
* Handles single message content generation and Web Push delivery.
|
|
633
641
|
* Receives its dependencies (encryption helpers, webpush, VAPID config)
|
|
@@ -824,32 +832,110 @@ async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, provided
|
|
|
824
832
|
* @private
|
|
825
833
|
*/
|
|
826
834
|
async function _callAI(payload) {
|
|
827
|
-
const
|
|
835
|
+
const normalizedApiUrl = normalizeAiApiUrl(payload.apiUrl);
|
|
836
|
+
const requestBody = buildAiRequestBody(payload);
|
|
837
|
+
|
|
838
|
+
const aiResponse = await fetch(normalizedApiUrl, {
|
|
828
839
|
method: 'POST',
|
|
829
840
|
headers: {
|
|
830
841
|
'Content-Type': 'application/json',
|
|
831
842
|
'Authorization': `Bearer ${payload.apiKey}`
|
|
832
843
|
},
|
|
833
|
-
body: JSON.stringify(
|
|
834
|
-
model: payload.primaryModel,
|
|
835
|
-
messages: [{ role: 'user', content: payload.completePrompt }],
|
|
836
|
-
max_tokens: 500,
|
|
837
|
-
temperature: 0.8
|
|
838
|
-
}),
|
|
844
|
+
body: JSON.stringify(requestBody),
|
|
839
845
|
signal: AbortSignal.timeout(300000)
|
|
840
846
|
});
|
|
841
847
|
|
|
842
848
|
if (!aiResponse.ok) {
|
|
843
|
-
|
|
849
|
+
if (aiResponse.status === 405) {
|
|
850
|
+
throw new Error(
|
|
851
|
+
`AI API error: 405 Method Not Allowed. ` +
|
|
852
|
+
`apiUrl must point to a full chat endpoint (for example: /chat/completions). ` +
|
|
853
|
+
`Received: ${normalizedApiUrl}`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
throw new Error(
|
|
858
|
+
`AI API error: ${aiResponse.status} ${aiResponse.statusText || 'Unknown Error'}. ` +
|
|
859
|
+
`Request URL: ${normalizedApiUrl}`
|
|
860
|
+
);
|
|
844
861
|
}
|
|
845
862
|
|
|
846
863
|
const aiData = await aiResponse.json();
|
|
847
|
-
|
|
864
|
+
const content = aiData?.choices?.[0]?.message?.content;
|
|
865
|
+
if (typeof content !== 'string' || !content.trim()) {
|
|
866
|
+
throw new Error('AI API error: response missing choices[0].message.content');
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return content.trim();
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Build OpenAI-compatible request body.
|
|
874
|
+
*
|
|
875
|
+
* `max_tokens` is optional:
|
|
876
|
+
* - include it only when payload.maxTokens is provided
|
|
877
|
+
* - omit it when payload.maxTokens is undefined / null
|
|
878
|
+
*
|
|
879
|
+
* @param {Object} payload
|
|
880
|
+
* @returns {Object}
|
|
881
|
+
*/
|
|
882
|
+
function buildAiRequestBody(payload) {
|
|
883
|
+
const requestBody = {
|
|
884
|
+
model: payload.primaryModel,
|
|
885
|
+
messages: [{ role: 'user', content: payload.completePrompt }],
|
|
886
|
+
temperature: 0.8
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
if (payload.maxTokens === undefined || payload.maxTokens === null) {
|
|
890
|
+
return requestBody;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (!Number.isInteger(payload.maxTokens) || payload.maxTokens <= 0) {
|
|
894
|
+
throw new Error('Invalid maxTokens: maxTokens must be a positive integer when provided.');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
requestBody.max_tokens = payload.maxTokens;
|
|
898
|
+
return requestBody;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Normalize AI API URL with minimal, non-breaking rules:
|
|
903
|
+
* - trim whitespace
|
|
904
|
+
* - remove redundant trailing slashes from pathname
|
|
905
|
+
* - keep query string untouched
|
|
906
|
+
*
|
|
907
|
+
* Note: This function does NOT auto-append /v1 or /chat/completions.
|
|
908
|
+
*
|
|
909
|
+
* @param {string} apiUrl
|
|
910
|
+
* @returns {string}
|
|
911
|
+
*/
|
|
912
|
+
function normalizeAiApiUrl(apiUrl) {
|
|
913
|
+
if (typeof apiUrl !== 'string' || !apiUrl.trim()) {
|
|
914
|
+
throw new Error(
|
|
915
|
+
'Invalid apiUrl: apiUrl is required. ' +
|
|
916
|
+
'Please provide a full chat endpoint URL (for example: https://api.openai.com/v1/chat/completions).'
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const trimmedApiUrl = apiUrl.trim();
|
|
921
|
+
let parsedUrl;
|
|
922
|
+
|
|
923
|
+
try {
|
|
924
|
+
parsedUrl = new URL(trimmedApiUrl);
|
|
925
|
+
} catch {
|
|
926
|
+
throw new Error(
|
|
927
|
+
`Invalid apiUrl: "${apiUrl}". ` +
|
|
928
|
+
'Please provide a valid absolute URL that points to a full chat endpoint.'
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, '') || '/';
|
|
933
|
+
return parsedUrl.toString();
|
|
848
934
|
}
|
|
849
935
|
|
|
850
936
|
/**
|
|
851
937
|
* Handler: schedule-message
|
|
852
|
-
* ReiStandard SDK v2.0.
|
|
938
|
+
* ReiStandard SDK v2.0.1
|
|
853
939
|
*
|
|
854
940
|
* @param {Object} ctx - Server context.
|
|
855
941
|
* @returns {{ POST: function }}
|
|
@@ -933,6 +1019,7 @@ function createScheduleMessageHandler(ctx) {
|
|
|
933
1019
|
apiKey: payload.apiKey || null,
|
|
934
1020
|
primaryModel: payload.primaryModel || null,
|
|
935
1021
|
completePrompt: payload.completePrompt || null,
|
|
1022
|
+
maxTokens: payload.maxTokens ?? null,
|
|
936
1023
|
pushSubscription: payload.pushSubscription,
|
|
937
1024
|
metadata: payload.metadata || {}
|
|
938
1025
|
};
|
|
@@ -1046,7 +1133,7 @@ function createScheduleMessageHandler(ctx) {
|
|
|
1046
1133
|
|
|
1047
1134
|
/**
|
|
1048
1135
|
* Handler: send-notifications
|
|
1049
|
-
* ReiStandard SDK v2.0.
|
|
1136
|
+
* ReiStandard SDK v2.0.1
|
|
1050
1137
|
*
|
|
1051
1138
|
* @param {Object} ctx - Server context.
|
|
1052
1139
|
* @returns {{ POST: function }}
|
|
@@ -1236,7 +1323,7 @@ function createSendNotificationsHandler(ctx) {
|
|
|
1236
1323
|
|
|
1237
1324
|
/**
|
|
1238
1325
|
* Handler: update-message
|
|
1239
|
-
* ReiStandard SDK v2.0.
|
|
1326
|
+
* ReiStandard SDK v2.0.1
|
|
1240
1327
|
*
|
|
1241
1328
|
* @param {Object} ctx - Server context.
|
|
1242
1329
|
* @returns {{ PUT: function }}
|
|
@@ -1306,6 +1393,14 @@ function createUpdateMessageHandler(ctx) {
|
|
|
1306
1393
|
return { status: 400, body: { success: false, error: { code: 'INVALID_UPDATE_DATA', message: '更新数据格式错误', details: { invalidFields: ['recurrenceType'] } } } };
|
|
1307
1394
|
}
|
|
1308
1395
|
|
|
1396
|
+
if (
|
|
1397
|
+
Object.prototype.hasOwnProperty.call(updates, 'maxTokens') &&
|
|
1398
|
+
updates.maxTokens !== null &&
|
|
1399
|
+
(!Number.isInteger(updates.maxTokens) || updates.maxTokens <= 0)
|
|
1400
|
+
) {
|
|
1401
|
+
return { status: 400, body: { success: false, error: { code: 'INVALID_UPDATE_DATA', message: '更新数据格式错误', details: { invalidFields: ['maxTokens'] } } } };
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1309
1404
|
// Fetch existing task
|
|
1310
1405
|
const existingTask = await db.getTaskByUuid(taskUuid, userId);
|
|
1311
1406
|
|
|
@@ -1325,7 +1420,8 @@ function createUpdateMessageHandler(ctx) {
|
|
|
1325
1420
|
...(updates.userMessage && { userMessage: updates.userMessage }),
|
|
1326
1421
|
...(updates.recurrenceType && { recurrenceType: updates.recurrenceType }),
|
|
1327
1422
|
...(updates.avatarUrl && { avatarUrl: updates.avatarUrl }),
|
|
1328
|
-
...(updates.metadata && { metadata: updates.metadata })
|
|
1423
|
+
...(updates.metadata && { metadata: updates.metadata }),
|
|
1424
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'maxTokens') && { maxTokens: updates.maxTokens ?? null })
|
|
1329
1425
|
};
|
|
1330
1426
|
|
|
1331
1427
|
const encryptedPayload = encryptForStorage(JSON.stringify(updatedData), userKey);
|
|
@@ -1355,7 +1451,7 @@ function createUpdateMessageHandler(ctx) {
|
|
|
1355
1451
|
|
|
1356
1452
|
/**
|
|
1357
1453
|
* Handler: cancel-message
|
|
1358
|
-
* ReiStandard SDK v2.0.
|
|
1454
|
+
* ReiStandard SDK v2.0.1
|
|
1359
1455
|
*
|
|
1360
1456
|
* @param {Object} ctx - Server context.
|
|
1361
1457
|
* @returns {{ DELETE: function }}
|
|
@@ -1408,7 +1504,7 @@ function createCancelMessageHandler(ctx) {
|
|
|
1408
1504
|
|
|
1409
1505
|
/**
|
|
1410
1506
|
* Handler: messages
|
|
1411
|
-
* ReiStandard SDK v2.0.
|
|
1507
|
+
* ReiStandard SDK v2.0.1
|
|
1412
1508
|
*
|
|
1413
1509
|
* @param {Object} ctx - Server context.
|
|
1414
1510
|
* @returns {{ GET: function }}
|
|
@@ -1911,7 +2007,7 @@ function createTenantContextManager(options) {
|
|
|
1911
2007
|
|
|
1912
2008
|
/**
|
|
1913
2009
|
* ReiStandard Server SDK Entry Point
|
|
1914
|
-
* v2.0.
|
|
2010
|
+
* v2.0.1
|
|
1915
2011
|
*
|
|
1916
2012
|
* Usage:
|
|
1917
2013
|
* import { createReiServer } from '@rei-standard/amsg-server';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createDecipheriv, createHash, randomBytes, createCipheriv, randomUUID,
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adapter Factory
|
|
5
|
-
* ReiStandard SDK v2.0.
|
|
5
|
+
* ReiStandard SDK v2.0.1
|
|
6
6
|
*
|
|
7
7
|
* Creates a database adapter instance based on the supplied configuration.
|
|
8
8
|
*
|
|
@@ -35,12 +35,12 @@ async function createAdapter(config) {
|
|
|
35
35
|
|
|
36
36
|
switch (config.driver) {
|
|
37
37
|
case 'neon': {
|
|
38
|
-
const { NeonAdapter } = await import('./neon-
|
|
38
|
+
const { NeonAdapter } = await import('./neon-DYvGnCzx.d.ts');
|
|
39
39
|
return new NeonAdapter(config.connectionString);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
case 'pg': {
|
|
43
|
-
const { PgAdapter } = await import('./pg-
|
|
43
|
+
const { PgAdapter } = await import('./pg-Du-pN_UT.d.ts');
|
|
44
44
|
return new PgAdapter(config.connectionString);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -54,7 +54,7 @@ async function createAdapter(config) {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Validation utility library (SDK version)
|
|
57
|
-
* ReiStandard SDK v2.0.
|
|
57
|
+
* ReiStandard SDK v2.0.1
|
|
58
58
|
*/
|
|
59
59
|
|
|
60
60
|
/**
|
|
@@ -132,6 +132,14 @@ function validateScheduleMessagePayload(payload) {
|
|
|
132
132
|
return { valid: false, errorCode: 'INVALID_PARAMETERS', errorMessage: '缺少必需参数或参数格式错误', details: { invalidFields: ['recurrenceType'] } };
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
if (
|
|
136
|
+
payload.maxTokens !== undefined &&
|
|
137
|
+
payload.maxTokens !== null &&
|
|
138
|
+
(!Number.isInteger(payload.maxTokens) || payload.maxTokens <= 0)
|
|
139
|
+
) {
|
|
140
|
+
return { valid: false, errorCode: 'INVALID_PARAMETERS', errorMessage: '缺少必需参数或参数格式错误', details: { invalidFields: ['maxTokens'] } };
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
if (payload.messageType === 'fixed') {
|
|
136
144
|
if (!payload.userMessage) {
|
|
137
145
|
return { valid: false, errorCode: 'INVALID_PARAMETERS', errorMessage: '缺少必需参数或参数格式错误', details: { missingFields: ['userMessage (required for fixed type)'] } };
|
|
@@ -320,7 +328,7 @@ function getHeader(headers = {}, name) {
|
|
|
320
328
|
|
|
321
329
|
/**
|
|
322
330
|
* Handler: init-tenant
|
|
323
|
-
* ReiStandard SDK v2.0.
|
|
331
|
+
* ReiStandard SDK v2.0.1
|
|
324
332
|
*
|
|
325
333
|
* @param {Object} ctx - Server context.
|
|
326
334
|
* @returns {{ POST: function }}
|
|
@@ -456,7 +464,7 @@ function createInitTenantHandler(ctx) {
|
|
|
456
464
|
|
|
457
465
|
/**
|
|
458
466
|
* Encryption utility library (SDK version)
|
|
459
|
-
* ReiStandard SDK v2.0.
|
|
467
|
+
* ReiStandard SDK v2.0.1
|
|
460
468
|
*
|
|
461
469
|
* Wraps AES-256-GCM operations for request/response and storage encryption.
|
|
462
470
|
*/
|
|
@@ -558,7 +566,7 @@ function decryptFromStorage(encryptedText, encryptionKey) {
|
|
|
558
566
|
|
|
559
567
|
/**
|
|
560
568
|
* Handler: get-user-key
|
|
561
|
-
* ReiStandard SDK v2.0.
|
|
569
|
+
* ReiStandard SDK v2.0.1
|
|
562
570
|
*
|
|
563
571
|
* @param {Object} ctx - Server context.
|
|
564
572
|
* @returns {{ GET: function }}
|
|
@@ -627,7 +635,7 @@ function isUniqueViolation(error) {
|
|
|
627
635
|
|
|
628
636
|
/**
|
|
629
637
|
* Message Processor (SDK version)
|
|
630
|
-
* ReiStandard SDK v2.0.
|
|
638
|
+
* ReiStandard SDK v2.0.1
|
|
631
639
|
*
|
|
632
640
|
* Handles single message content generation and Web Push delivery.
|
|
633
641
|
* Receives its dependencies (encryption helpers, webpush, VAPID config)
|
|
@@ -824,32 +832,110 @@ async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, provided
|
|
|
824
832
|
* @private
|
|
825
833
|
*/
|
|
826
834
|
async function _callAI(payload) {
|
|
827
|
-
const
|
|
835
|
+
const normalizedApiUrl = normalizeAiApiUrl(payload.apiUrl);
|
|
836
|
+
const requestBody = buildAiRequestBody(payload);
|
|
837
|
+
|
|
838
|
+
const aiResponse = await fetch(normalizedApiUrl, {
|
|
828
839
|
method: 'POST',
|
|
829
840
|
headers: {
|
|
830
841
|
'Content-Type': 'application/json',
|
|
831
842
|
'Authorization': `Bearer ${payload.apiKey}`
|
|
832
843
|
},
|
|
833
|
-
body: JSON.stringify(
|
|
834
|
-
model: payload.primaryModel,
|
|
835
|
-
messages: [{ role: 'user', content: payload.completePrompt }],
|
|
836
|
-
max_tokens: 500,
|
|
837
|
-
temperature: 0.8
|
|
838
|
-
}),
|
|
844
|
+
body: JSON.stringify(requestBody),
|
|
839
845
|
signal: AbortSignal.timeout(300000)
|
|
840
846
|
});
|
|
841
847
|
|
|
842
848
|
if (!aiResponse.ok) {
|
|
843
|
-
|
|
849
|
+
if (aiResponse.status === 405) {
|
|
850
|
+
throw new Error(
|
|
851
|
+
`AI API error: 405 Method Not Allowed. ` +
|
|
852
|
+
`apiUrl must point to a full chat endpoint (for example: /chat/completions). ` +
|
|
853
|
+
`Received: ${normalizedApiUrl}`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
throw new Error(
|
|
858
|
+
`AI API error: ${aiResponse.status} ${aiResponse.statusText || 'Unknown Error'}. ` +
|
|
859
|
+
`Request URL: ${normalizedApiUrl}`
|
|
860
|
+
);
|
|
844
861
|
}
|
|
845
862
|
|
|
846
863
|
const aiData = await aiResponse.json();
|
|
847
|
-
|
|
864
|
+
const content = aiData?.choices?.[0]?.message?.content;
|
|
865
|
+
if (typeof content !== 'string' || !content.trim()) {
|
|
866
|
+
throw new Error('AI API error: response missing choices[0].message.content');
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return content.trim();
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Build OpenAI-compatible request body.
|
|
874
|
+
*
|
|
875
|
+
* `max_tokens` is optional:
|
|
876
|
+
* - include it only when payload.maxTokens is provided
|
|
877
|
+
* - omit it when payload.maxTokens is undefined / null
|
|
878
|
+
*
|
|
879
|
+
* @param {Object} payload
|
|
880
|
+
* @returns {Object}
|
|
881
|
+
*/
|
|
882
|
+
function buildAiRequestBody(payload) {
|
|
883
|
+
const requestBody = {
|
|
884
|
+
model: payload.primaryModel,
|
|
885
|
+
messages: [{ role: 'user', content: payload.completePrompt }],
|
|
886
|
+
temperature: 0.8
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
if (payload.maxTokens === undefined || payload.maxTokens === null) {
|
|
890
|
+
return requestBody;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (!Number.isInteger(payload.maxTokens) || payload.maxTokens <= 0) {
|
|
894
|
+
throw new Error('Invalid maxTokens: maxTokens must be a positive integer when provided.');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
requestBody.max_tokens = payload.maxTokens;
|
|
898
|
+
return requestBody;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Normalize AI API URL with minimal, non-breaking rules:
|
|
903
|
+
* - trim whitespace
|
|
904
|
+
* - remove redundant trailing slashes from pathname
|
|
905
|
+
* - keep query string untouched
|
|
906
|
+
*
|
|
907
|
+
* Note: This function does NOT auto-append /v1 or /chat/completions.
|
|
908
|
+
*
|
|
909
|
+
* @param {string} apiUrl
|
|
910
|
+
* @returns {string}
|
|
911
|
+
*/
|
|
912
|
+
function normalizeAiApiUrl(apiUrl) {
|
|
913
|
+
if (typeof apiUrl !== 'string' || !apiUrl.trim()) {
|
|
914
|
+
throw new Error(
|
|
915
|
+
'Invalid apiUrl: apiUrl is required. ' +
|
|
916
|
+
'Please provide a full chat endpoint URL (for example: https://api.openai.com/v1/chat/completions).'
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const trimmedApiUrl = apiUrl.trim();
|
|
921
|
+
let parsedUrl;
|
|
922
|
+
|
|
923
|
+
try {
|
|
924
|
+
parsedUrl = new URL(trimmedApiUrl);
|
|
925
|
+
} catch {
|
|
926
|
+
throw new Error(
|
|
927
|
+
`Invalid apiUrl: "${apiUrl}". ` +
|
|
928
|
+
'Please provide a valid absolute URL that points to a full chat endpoint.'
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, '') || '/';
|
|
933
|
+
return parsedUrl.toString();
|
|
848
934
|
}
|
|
849
935
|
|
|
850
936
|
/**
|
|
851
937
|
* Handler: schedule-message
|
|
852
|
-
* ReiStandard SDK v2.0.
|
|
938
|
+
* ReiStandard SDK v2.0.1
|
|
853
939
|
*
|
|
854
940
|
* @param {Object} ctx - Server context.
|
|
855
941
|
* @returns {{ POST: function }}
|
|
@@ -933,6 +1019,7 @@ function createScheduleMessageHandler(ctx) {
|
|
|
933
1019
|
apiKey: payload.apiKey || null,
|
|
934
1020
|
primaryModel: payload.primaryModel || null,
|
|
935
1021
|
completePrompt: payload.completePrompt || null,
|
|
1022
|
+
maxTokens: payload.maxTokens ?? null,
|
|
936
1023
|
pushSubscription: payload.pushSubscription,
|
|
937
1024
|
metadata: payload.metadata || {}
|
|
938
1025
|
};
|
|
@@ -1046,7 +1133,7 @@ function createScheduleMessageHandler(ctx) {
|
|
|
1046
1133
|
|
|
1047
1134
|
/**
|
|
1048
1135
|
* Handler: send-notifications
|
|
1049
|
-
* ReiStandard SDK v2.0.
|
|
1136
|
+
* ReiStandard SDK v2.0.1
|
|
1050
1137
|
*
|
|
1051
1138
|
* @param {Object} ctx - Server context.
|
|
1052
1139
|
* @returns {{ POST: function }}
|
|
@@ -1236,7 +1323,7 @@ function createSendNotificationsHandler(ctx) {
|
|
|
1236
1323
|
|
|
1237
1324
|
/**
|
|
1238
1325
|
* Handler: update-message
|
|
1239
|
-
* ReiStandard SDK v2.0.
|
|
1326
|
+
* ReiStandard SDK v2.0.1
|
|
1240
1327
|
*
|
|
1241
1328
|
* @param {Object} ctx - Server context.
|
|
1242
1329
|
* @returns {{ PUT: function }}
|
|
@@ -1306,6 +1393,14 @@ function createUpdateMessageHandler(ctx) {
|
|
|
1306
1393
|
return { status: 400, body: { success: false, error: { code: 'INVALID_UPDATE_DATA', message: '更新数据格式错误', details: { invalidFields: ['recurrenceType'] } } } };
|
|
1307
1394
|
}
|
|
1308
1395
|
|
|
1396
|
+
if (
|
|
1397
|
+
Object.prototype.hasOwnProperty.call(updates, 'maxTokens') &&
|
|
1398
|
+
updates.maxTokens !== null &&
|
|
1399
|
+
(!Number.isInteger(updates.maxTokens) || updates.maxTokens <= 0)
|
|
1400
|
+
) {
|
|
1401
|
+
return { status: 400, body: { success: false, error: { code: 'INVALID_UPDATE_DATA', message: '更新数据格式错误', details: { invalidFields: ['maxTokens'] } } } };
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1309
1404
|
// Fetch existing task
|
|
1310
1405
|
const existingTask = await db.getTaskByUuid(taskUuid, userId);
|
|
1311
1406
|
|
|
@@ -1325,7 +1420,8 @@ function createUpdateMessageHandler(ctx) {
|
|
|
1325
1420
|
...(updates.userMessage && { userMessage: updates.userMessage }),
|
|
1326
1421
|
...(updates.recurrenceType && { recurrenceType: updates.recurrenceType }),
|
|
1327
1422
|
...(updates.avatarUrl && { avatarUrl: updates.avatarUrl }),
|
|
1328
|
-
...(updates.metadata && { metadata: updates.metadata })
|
|
1423
|
+
...(updates.metadata && { metadata: updates.metadata }),
|
|
1424
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'maxTokens') && { maxTokens: updates.maxTokens ?? null })
|
|
1329
1425
|
};
|
|
1330
1426
|
|
|
1331
1427
|
const encryptedPayload = encryptForStorage(JSON.stringify(updatedData), userKey);
|
|
@@ -1355,7 +1451,7 @@ function createUpdateMessageHandler(ctx) {
|
|
|
1355
1451
|
|
|
1356
1452
|
/**
|
|
1357
1453
|
* Handler: cancel-message
|
|
1358
|
-
* ReiStandard SDK v2.0.
|
|
1454
|
+
* ReiStandard SDK v2.0.1
|
|
1359
1455
|
*
|
|
1360
1456
|
* @param {Object} ctx - Server context.
|
|
1361
1457
|
* @returns {{ DELETE: function }}
|
|
@@ -1408,7 +1504,7 @@ function createCancelMessageHandler(ctx) {
|
|
|
1408
1504
|
|
|
1409
1505
|
/**
|
|
1410
1506
|
* Handler: messages
|
|
1411
|
-
* ReiStandard SDK v2.0.
|
|
1507
|
+
* ReiStandard SDK v2.0.1
|
|
1412
1508
|
*
|
|
1413
1509
|
* @param {Object} ctx - Server context.
|
|
1414
1510
|
* @returns {{ GET: function }}
|
|
@@ -1911,7 +2007,7 @@ function createTenantContextManager(options) {
|
|
|
1911
2007
|
|
|
1912
2008
|
/**
|
|
1913
2009
|
* ReiStandard Server SDK Entry Point
|
|
1914
|
-
* v2.0.
|
|
2010
|
+
* v2.0.1
|
|
1915
2011
|
*
|
|
1916
2012
|
* Usage:
|
|
1917
2013
|
* import { createReiServer } from '@rei-standard/amsg-server';
|
package/dist/index.mjs
CHANGED
|
@@ -66,6 +66,9 @@ function validateScheduleMessagePayload(payload) {
|
|
|
66
66
|
if (payload.recurrenceType && !["none", "daily", "weekly"].includes(payload.recurrenceType)) {
|
|
67
67
|
return { valid: false, errorCode: "INVALID_PARAMETERS", errorMessage: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\u6216\u53C2\u6570\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["recurrenceType"] } };
|
|
68
68
|
}
|
|
69
|
+
if (payload.maxTokens !== void 0 && payload.maxTokens !== null && (!Number.isInteger(payload.maxTokens) || payload.maxTokens <= 0)) {
|
|
70
|
+
return { valid: false, errorCode: "INVALID_PARAMETERS", errorMessage: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\u6216\u53C2\u6570\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["maxTokens"] } };
|
|
71
|
+
}
|
|
69
72
|
if (payload.messageType === "fixed") {
|
|
70
73
|
if (!payload.userMessage) {
|
|
71
74
|
return { valid: false, errorCode: "INVALID_PARAMETERS", errorMessage: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\u6216\u53C2\u6570\u683C\u5F0F\u9519\u8BEF", details: { missingFields: ["userMessage (required for fixed type)"] } };
|
|
@@ -505,25 +508,66 @@ async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, provided
|
|
|
505
508
|
}
|
|
506
509
|
}
|
|
507
510
|
async function _callAI(payload) {
|
|
508
|
-
const
|
|
511
|
+
const normalizedApiUrl = normalizeAiApiUrl(payload.apiUrl);
|
|
512
|
+
const requestBody = buildAiRequestBody(payload);
|
|
513
|
+
const aiResponse = await fetch(normalizedApiUrl, {
|
|
509
514
|
method: "POST",
|
|
510
515
|
headers: {
|
|
511
516
|
"Content-Type": "application/json",
|
|
512
517
|
"Authorization": `Bearer ${payload.apiKey}`
|
|
513
518
|
},
|
|
514
|
-
body: JSON.stringify(
|
|
515
|
-
model: payload.primaryModel,
|
|
516
|
-
messages: [{ role: "user", content: payload.completePrompt }],
|
|
517
|
-
max_tokens: 500,
|
|
518
|
-
temperature: 0.8
|
|
519
|
-
}),
|
|
519
|
+
body: JSON.stringify(requestBody),
|
|
520
520
|
signal: AbortSignal.timeout(3e5)
|
|
521
521
|
});
|
|
522
522
|
if (!aiResponse.ok) {
|
|
523
|
-
|
|
523
|
+
if (aiResponse.status === 405) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`AI API error: 405 Method Not Allowed. apiUrl must point to a full chat endpoint (for example: /chat/completions). Received: ${normalizedApiUrl}`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
throw new Error(
|
|
529
|
+
`AI API error: ${aiResponse.status} ${aiResponse.statusText || "Unknown Error"}. Request URL: ${normalizedApiUrl}`
|
|
530
|
+
);
|
|
524
531
|
}
|
|
525
532
|
const aiData = await aiResponse.json();
|
|
526
|
-
|
|
533
|
+
const content = aiData?.choices?.[0]?.message?.content;
|
|
534
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
535
|
+
throw new Error("AI API error: response missing choices[0].message.content");
|
|
536
|
+
}
|
|
537
|
+
return content.trim();
|
|
538
|
+
}
|
|
539
|
+
function buildAiRequestBody(payload) {
|
|
540
|
+
const requestBody = {
|
|
541
|
+
model: payload.primaryModel,
|
|
542
|
+
messages: [{ role: "user", content: payload.completePrompt }],
|
|
543
|
+
temperature: 0.8
|
|
544
|
+
};
|
|
545
|
+
if (payload.maxTokens === void 0 || payload.maxTokens === null) {
|
|
546
|
+
return requestBody;
|
|
547
|
+
}
|
|
548
|
+
if (!Number.isInteger(payload.maxTokens) || payload.maxTokens <= 0) {
|
|
549
|
+
throw new Error("Invalid maxTokens: maxTokens must be a positive integer when provided.");
|
|
550
|
+
}
|
|
551
|
+
requestBody.max_tokens = payload.maxTokens;
|
|
552
|
+
return requestBody;
|
|
553
|
+
}
|
|
554
|
+
function normalizeAiApiUrl(apiUrl) {
|
|
555
|
+
if (typeof apiUrl !== "string" || !apiUrl.trim()) {
|
|
556
|
+
throw new Error(
|
|
557
|
+
"Invalid apiUrl: apiUrl is required. Please provide a full chat endpoint URL (for example: https://api.openai.com/v1/chat/completions)."
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const trimmedApiUrl = apiUrl.trim();
|
|
561
|
+
let parsedUrl;
|
|
562
|
+
try {
|
|
563
|
+
parsedUrl = new URL(trimmedApiUrl);
|
|
564
|
+
} catch {
|
|
565
|
+
throw new Error(
|
|
566
|
+
`Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL that points to a full chat endpoint.`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, "") || "/";
|
|
570
|
+
return parsedUrl.toString();
|
|
527
571
|
}
|
|
528
572
|
|
|
529
573
|
// src/server/handlers/schedule-message.js
|
|
@@ -591,6 +635,7 @@ function createScheduleMessageHandler(ctx) {
|
|
|
591
635
|
apiKey: payload.apiKey || null,
|
|
592
636
|
primaryModel: payload.primaryModel || null,
|
|
593
637
|
completePrompt: payload.completePrompt || null,
|
|
638
|
+
maxTokens: payload.maxTokens ?? null,
|
|
594
639
|
pushSubscription: payload.pushSubscription,
|
|
595
640
|
metadata: payload.metadata || {}
|
|
596
641
|
};
|
|
@@ -899,6 +944,9 @@ function createUpdateMessageHandler(ctx) {
|
|
|
899
944
|
if (updates.recurrenceType && !["none", "daily", "weekly"].includes(updates.recurrenceType)) {
|
|
900
945
|
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["recurrenceType"] } } } };
|
|
901
946
|
}
|
|
947
|
+
if (Object.prototype.hasOwnProperty.call(updates, "maxTokens") && updates.maxTokens !== null && (!Number.isInteger(updates.maxTokens) || updates.maxTokens <= 0)) {
|
|
948
|
+
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["maxTokens"] } } } };
|
|
949
|
+
}
|
|
902
950
|
const existingTask = await db.getTaskByUuid(taskUuid, userId);
|
|
903
951
|
if (!existingTask) {
|
|
904
952
|
const taskStatus = await db.getTaskStatus(taskUuid, userId);
|
|
@@ -914,7 +962,8 @@ function createUpdateMessageHandler(ctx) {
|
|
|
914
962
|
...updates.userMessage && { userMessage: updates.userMessage },
|
|
915
963
|
...updates.recurrenceType && { recurrenceType: updates.recurrenceType },
|
|
916
964
|
...updates.avatarUrl && { avatarUrl: updates.avatarUrl },
|
|
917
|
-
...updates.metadata && { metadata: updates.metadata }
|
|
965
|
+
...updates.metadata && { metadata: updates.metadata },
|
|
966
|
+
...Object.prototype.hasOwnProperty.call(updates, "maxTokens") && { maxTokens: updates.maxTokens ?? null }
|
|
918
967
|
};
|
|
919
968
|
const encryptedPayload = encryptForStorage(JSON.stringify(updatedData), userKey);
|
|
920
969
|
const extraFields = updates.nextSendAt ? { next_send_at: updates.nextSendAt } : void 0;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { neon } from '@neondatabase/serverless';
|
|
2
|
-
import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './schema-
|
|
2
|
+
import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './schema-C8OnYk6j.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Neon Serverless Database Adapter
|
|
6
|
-
* ReiStandard SDK v2.0.
|
|
6
|
+
* ReiStandard SDK v2.0.1
|
|
7
7
|
*
|
|
8
8
|
* @implements {import('./interface.js').DbAdapter}
|
|
9
9
|
*/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { neon } from '@neondatabase/serverless';
|
|
2
|
-
import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './schema-
|
|
2
|
+
import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './schema-C8OnYk6j.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Neon Serverless Database Adapter
|
|
6
|
-
* ReiStandard SDK v2.0.
|
|
6
|
+
* ReiStandard SDK v2.0.1
|
|
7
7
|
*
|
|
8
8
|
* @implements {import('./interface.js').DbAdapter}
|
|
9
9
|
*/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Pool } from 'pg';
|
|
2
|
-
import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './schema-
|
|
2
|
+
import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './schema-C8OnYk6j.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* PostgreSQL (pg) Database Adapter
|
|
6
|
-
* ReiStandard SDK v2.0.
|
|
6
|
+
* ReiStandard SDK v2.0.1
|
|
7
7
|
*
|
|
8
8
|
* Uses the standard 'pg' npm package.
|
|
9
9
|
*
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Pool } from 'pg';
|
|
2
|
-
import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './schema-
|
|
2
|
+
import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './schema-C8OnYk6j.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* PostgreSQL (pg) Database Adapter
|
|
6
|
-
* ReiStandard SDK v2.0.
|
|
6
|
+
* ReiStandard SDK v2.0.1
|
|
7
7
|
*
|
|
8
8
|
* Uses the standard 'pg' npm package.
|
|
9
9
|
*
|