@rei-standard/amsg-server 2.1.0 → 2.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/README.md +4 -0
- package/dist/index.cjs +10 -3
- package/dist/index.d.cts +51 -9
- package/dist/index.d.ts +51 -9
- package/dist/index.mjs +10 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,10 @@ const rei = await createReiServer({
|
|
|
50
50
|
// GET /api/v1/messages -> rei.handlers.messages.GET
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
## 关于 `messageType: 'instant'`
|
|
54
|
+
|
|
55
|
+
> **Note**:新代码的 instant 消息请用 [@rei-standard/amsg-instant](../instant/README.md),跳过本端点的"建任务 → 处理 → 删任务" DB 来回。本端点的 `instant` 分支为兼容保留,行为不变、不会有运行时警告。
|
|
56
|
+
|
|
53
57
|
## AI 接口 `apiUrl` 约束
|
|
54
58
|
|
|
55
59
|
当 `messageType` 为 `prompted` / `auto`,或 `instant` 使用 AI 配置时:
|
package/dist/index.cjs
CHANGED
|
@@ -554,7 +554,7 @@ function buildAiRequestBody(payload) {
|
|
|
554
554
|
function normalizeAiApiUrl(apiUrl) {
|
|
555
555
|
if (typeof apiUrl !== "string" || !apiUrl.trim()) {
|
|
556
556
|
throw new Error(
|
|
557
|
-
"Invalid apiUrl: apiUrl is required. Please provide a
|
|
557
|
+
"Invalid apiUrl: apiUrl is required. Please provide a chat endpoint URL (for example: https://api.openai.com or https://api.openai.com/v1/chat/completions)."
|
|
558
558
|
);
|
|
559
559
|
}
|
|
560
560
|
const trimmedApiUrl = apiUrl.trim();
|
|
@@ -563,10 +563,17 @@ function normalizeAiApiUrl(apiUrl) {
|
|
|
563
563
|
parsedUrl = new URL(trimmedApiUrl);
|
|
564
564
|
} catch (e3) {
|
|
565
565
|
throw new Error(
|
|
566
|
-
`Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL
|
|
566
|
+
`Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL.`
|
|
567
567
|
);
|
|
568
568
|
}
|
|
569
|
-
|
|
569
|
+
let path = parsedUrl.pathname.replace(/\/+$/, "") || "/";
|
|
570
|
+
if (/\/chat\/completions$/.test(path)) {
|
|
571
|
+
} else if (path === "/") {
|
|
572
|
+
path = "/v1/chat/completions";
|
|
573
|
+
} else if (/\/v\d+$/.test(path)) {
|
|
574
|
+
path = `${path}/chat/completions`;
|
|
575
|
+
}
|
|
576
|
+
parsedUrl.pathname = path;
|
|
570
577
|
return parsedUrl.toString();
|
|
571
578
|
}
|
|
572
579
|
|
package/dist/index.d.cts
CHANGED
|
@@ -906,12 +906,27 @@ function buildAiRequestBody(payload) {
|
|
|
906
906
|
}
|
|
907
907
|
|
|
908
908
|
/**
|
|
909
|
-
* Normalize AI API URL
|
|
910
|
-
* - trim whitespace
|
|
911
|
-
* - remove redundant trailing slashes from pathname
|
|
912
|
-
* - keep query string untouched
|
|
909
|
+
* Normalize AI API URL for OpenAI-compatible chat endpoints.
|
|
913
910
|
*
|
|
914
|
-
*
|
|
911
|
+
* **Keep in sync** with `@rei-standard/amsg-instant`'s
|
|
912
|
+
* `src/message-processor.js` `normalizeAiApiUrl` — same rules, same
|
|
913
|
+
* tests. The two packages share this logic but each carry their own copy
|
|
914
|
+
* to avoid an architectural dependency (server should not depend on the
|
|
915
|
+
* stateless worker package).
|
|
916
|
+
*
|
|
917
|
+
* Rules (idempotent — running it twice equals running it once):
|
|
918
|
+
* - Already ends with `/chat/completions` → leave as-is.
|
|
919
|
+
* - Bare host (no path or just `/`) → append `/v1/chat/completions`.
|
|
920
|
+
* - Path ends with a version segment like `/v1`,
|
|
921
|
+
* `/v2`, … (with or without trailing slash) → append only
|
|
922
|
+
* `/chat/completions`; never doubles `/v1` for
|
|
923
|
+
* callers who already include it.
|
|
924
|
+
* - Anything else (custom path that doesn't match the
|
|
925
|
+
* OpenAI shape, e.g. `/v1/messages` for
|
|
926
|
+
* Anthropic-style proxies) → leave as-is. The caller
|
|
927
|
+
* knows their own routing.
|
|
928
|
+
*
|
|
929
|
+
* Query string is preserved verbatim.
|
|
915
930
|
*
|
|
916
931
|
* @param {string} apiUrl
|
|
917
932
|
* @returns {string}
|
|
@@ -920,7 +935,8 @@ function normalizeAiApiUrl(apiUrl) {
|
|
|
920
935
|
if (typeof apiUrl !== 'string' || !apiUrl.trim()) {
|
|
921
936
|
throw new Error(
|
|
922
937
|
'Invalid apiUrl: apiUrl is required. ' +
|
|
923
|
-
'Please provide a
|
|
938
|
+
'Please provide a chat endpoint URL ' +
|
|
939
|
+
'(for example: https://api.openai.com or https://api.openai.com/v1/chat/completions).'
|
|
924
940
|
);
|
|
925
941
|
}
|
|
926
942
|
|
|
@@ -931,12 +947,23 @@ function normalizeAiApiUrl(apiUrl) {
|
|
|
931
947
|
parsedUrl = new URL(trimmedApiUrl);
|
|
932
948
|
} catch {
|
|
933
949
|
throw new Error(
|
|
934
|
-
`Invalid apiUrl: "${apiUrl}".
|
|
935
|
-
'Please provide a valid absolute URL that points to a full chat endpoint.'
|
|
950
|
+
`Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL.`
|
|
936
951
|
);
|
|
937
952
|
}
|
|
938
953
|
|
|
939
|
-
|
|
954
|
+
let path = parsedUrl.pathname.replace(/\/+$/, '') || '/';
|
|
955
|
+
|
|
956
|
+
if (/\/chat\/completions$/.test(path)) ; else if (path === '/') {
|
|
957
|
+
// Bare host → assume OpenAI shape.
|
|
958
|
+
path = '/v1/chat/completions';
|
|
959
|
+
} else if (/\/v\d+$/.test(path)) {
|
|
960
|
+
// Path ends in `/v1`, `/v2`, … — caller already versioned the URL.
|
|
961
|
+
// Append only `/chat/completions`; never re-add `/v1`.
|
|
962
|
+
path = `${path}/chat/completions`;
|
|
963
|
+
}
|
|
964
|
+
// Any other custom path is left untouched on purpose.
|
|
965
|
+
|
|
966
|
+
parsedUrl.pathname = path;
|
|
940
967
|
return parsedUrl.toString();
|
|
941
968
|
}
|
|
942
969
|
|
|
@@ -1033,6 +1060,14 @@ function createScheduleMessageHandler(ctx) {
|
|
|
1033
1060
|
|
|
1034
1061
|
const encryptedPayload = encryptForStorage(JSON.stringify(fullTaskData), userKey);
|
|
1035
1062
|
|
|
1063
|
+
/**
|
|
1064
|
+
* @deprecated Soft-deprecated. For new code, use @rei-standard/amsg-instant.
|
|
1065
|
+
* This branch is kept for backward compatibility and the existing behavior
|
|
1066
|
+
* (create task → process → delete) is unchanged. The dedicated amsg-instant
|
|
1067
|
+
* package is stateless (no DB roundtrip), deployable to Cloudflare Workers,
|
|
1068
|
+
* and locks the encryption + push-payload contract behind a single version.
|
|
1069
|
+
* See packages/rei-standard-amsg/instant/README.md.
|
|
1070
|
+
*/
|
|
1036
1071
|
// Instant type: check VAPID before creating the task to avoid orphaned rows
|
|
1037
1072
|
if (payload.messageType === 'instant') {
|
|
1038
1073
|
if (!ctx.vapid.email || !ctx.vapid.publicKey || !ctx.vapid.privateKey) {
|
|
@@ -1086,6 +1121,13 @@ function createScheduleMessageHandler(ctx) {
|
|
|
1086
1121
|
return { status: 500, body: { success: false, error: { code: 'TASK_CREATE_FAILED', message: '创建任务失败' } } };
|
|
1087
1122
|
}
|
|
1088
1123
|
|
|
1124
|
+
/**
|
|
1125
|
+
* @deprecated Soft-deprecated. For new code, use @rei-standard/amsg-instant.
|
|
1126
|
+
* The "create-task → process-by-uuid → delete-task" sequence below is
|
|
1127
|
+
* preserved verbatim so existing clients keep working. New integrations
|
|
1128
|
+
* should call the dedicated amsg-instant endpoint instead — it skips this
|
|
1129
|
+
* DB round-trip entirely. See packages/rei-standard-amsg/instant/README.md.
|
|
1130
|
+
*/
|
|
1089
1131
|
// Instant type: send immediately
|
|
1090
1132
|
if (payload.messageType === 'instant') {
|
|
1091
1133
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -906,12 +906,27 @@ function buildAiRequestBody(payload) {
|
|
|
906
906
|
}
|
|
907
907
|
|
|
908
908
|
/**
|
|
909
|
-
* Normalize AI API URL
|
|
910
|
-
* - trim whitespace
|
|
911
|
-
* - remove redundant trailing slashes from pathname
|
|
912
|
-
* - keep query string untouched
|
|
909
|
+
* Normalize AI API URL for OpenAI-compatible chat endpoints.
|
|
913
910
|
*
|
|
914
|
-
*
|
|
911
|
+
* **Keep in sync** with `@rei-standard/amsg-instant`'s
|
|
912
|
+
* `src/message-processor.js` `normalizeAiApiUrl` — same rules, same
|
|
913
|
+
* tests. The two packages share this logic but each carry their own copy
|
|
914
|
+
* to avoid an architectural dependency (server should not depend on the
|
|
915
|
+
* stateless worker package).
|
|
916
|
+
*
|
|
917
|
+
* Rules (idempotent — running it twice equals running it once):
|
|
918
|
+
* - Already ends with `/chat/completions` → leave as-is.
|
|
919
|
+
* - Bare host (no path or just `/`) → append `/v1/chat/completions`.
|
|
920
|
+
* - Path ends with a version segment like `/v1`,
|
|
921
|
+
* `/v2`, … (with or without trailing slash) → append only
|
|
922
|
+
* `/chat/completions`; never doubles `/v1` for
|
|
923
|
+
* callers who already include it.
|
|
924
|
+
* - Anything else (custom path that doesn't match the
|
|
925
|
+
* OpenAI shape, e.g. `/v1/messages` for
|
|
926
|
+
* Anthropic-style proxies) → leave as-is. The caller
|
|
927
|
+
* knows their own routing.
|
|
928
|
+
*
|
|
929
|
+
* Query string is preserved verbatim.
|
|
915
930
|
*
|
|
916
931
|
* @param {string} apiUrl
|
|
917
932
|
* @returns {string}
|
|
@@ -920,7 +935,8 @@ function normalizeAiApiUrl(apiUrl) {
|
|
|
920
935
|
if (typeof apiUrl !== 'string' || !apiUrl.trim()) {
|
|
921
936
|
throw new Error(
|
|
922
937
|
'Invalid apiUrl: apiUrl is required. ' +
|
|
923
|
-
'Please provide a
|
|
938
|
+
'Please provide a chat endpoint URL ' +
|
|
939
|
+
'(for example: https://api.openai.com or https://api.openai.com/v1/chat/completions).'
|
|
924
940
|
);
|
|
925
941
|
}
|
|
926
942
|
|
|
@@ -931,12 +947,23 @@ function normalizeAiApiUrl(apiUrl) {
|
|
|
931
947
|
parsedUrl = new URL(trimmedApiUrl);
|
|
932
948
|
} catch {
|
|
933
949
|
throw new Error(
|
|
934
|
-
`Invalid apiUrl: "${apiUrl}".
|
|
935
|
-
'Please provide a valid absolute URL that points to a full chat endpoint.'
|
|
950
|
+
`Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL.`
|
|
936
951
|
);
|
|
937
952
|
}
|
|
938
953
|
|
|
939
|
-
|
|
954
|
+
let path = parsedUrl.pathname.replace(/\/+$/, '') || '/';
|
|
955
|
+
|
|
956
|
+
if (/\/chat\/completions$/.test(path)) ; else if (path === '/') {
|
|
957
|
+
// Bare host → assume OpenAI shape.
|
|
958
|
+
path = '/v1/chat/completions';
|
|
959
|
+
} else if (/\/v\d+$/.test(path)) {
|
|
960
|
+
// Path ends in `/v1`, `/v2`, … — caller already versioned the URL.
|
|
961
|
+
// Append only `/chat/completions`; never re-add `/v1`.
|
|
962
|
+
path = `${path}/chat/completions`;
|
|
963
|
+
}
|
|
964
|
+
// Any other custom path is left untouched on purpose.
|
|
965
|
+
|
|
966
|
+
parsedUrl.pathname = path;
|
|
940
967
|
return parsedUrl.toString();
|
|
941
968
|
}
|
|
942
969
|
|
|
@@ -1033,6 +1060,14 @@ function createScheduleMessageHandler(ctx) {
|
|
|
1033
1060
|
|
|
1034
1061
|
const encryptedPayload = encryptForStorage(JSON.stringify(fullTaskData), userKey);
|
|
1035
1062
|
|
|
1063
|
+
/**
|
|
1064
|
+
* @deprecated Soft-deprecated. For new code, use @rei-standard/amsg-instant.
|
|
1065
|
+
* This branch is kept for backward compatibility and the existing behavior
|
|
1066
|
+
* (create task → process → delete) is unchanged. The dedicated amsg-instant
|
|
1067
|
+
* package is stateless (no DB roundtrip), deployable to Cloudflare Workers,
|
|
1068
|
+
* and locks the encryption + push-payload contract behind a single version.
|
|
1069
|
+
* See packages/rei-standard-amsg/instant/README.md.
|
|
1070
|
+
*/
|
|
1036
1071
|
// Instant type: check VAPID before creating the task to avoid orphaned rows
|
|
1037
1072
|
if (payload.messageType === 'instant') {
|
|
1038
1073
|
if (!ctx.vapid.email || !ctx.vapid.publicKey || !ctx.vapid.privateKey) {
|
|
@@ -1086,6 +1121,13 @@ function createScheduleMessageHandler(ctx) {
|
|
|
1086
1121
|
return { status: 500, body: { success: false, error: { code: 'TASK_CREATE_FAILED', message: '创建任务失败' } } };
|
|
1087
1122
|
}
|
|
1088
1123
|
|
|
1124
|
+
/**
|
|
1125
|
+
* @deprecated Soft-deprecated. For new code, use @rei-standard/amsg-instant.
|
|
1126
|
+
* The "create-task → process-by-uuid → delete-task" sequence below is
|
|
1127
|
+
* preserved verbatim so existing clients keep working. New integrations
|
|
1128
|
+
* should call the dedicated amsg-instant endpoint instead — it skips this
|
|
1129
|
+
* DB round-trip entirely. See packages/rei-standard-amsg/instant/README.md.
|
|
1130
|
+
*/
|
|
1089
1131
|
// Instant type: send immediately
|
|
1090
1132
|
if (payload.messageType === 'instant') {
|
|
1091
1133
|
try {
|
package/dist/index.mjs
CHANGED
|
@@ -554,7 +554,7 @@ function buildAiRequestBody(payload) {
|
|
|
554
554
|
function normalizeAiApiUrl(apiUrl) {
|
|
555
555
|
if (typeof apiUrl !== "string" || !apiUrl.trim()) {
|
|
556
556
|
throw new Error(
|
|
557
|
-
"Invalid apiUrl: apiUrl is required. Please provide a
|
|
557
|
+
"Invalid apiUrl: apiUrl is required. Please provide a chat endpoint URL (for example: https://api.openai.com or https://api.openai.com/v1/chat/completions)."
|
|
558
558
|
);
|
|
559
559
|
}
|
|
560
560
|
const trimmedApiUrl = apiUrl.trim();
|
|
@@ -563,10 +563,17 @@ function normalizeAiApiUrl(apiUrl) {
|
|
|
563
563
|
parsedUrl = new URL(trimmedApiUrl);
|
|
564
564
|
} catch {
|
|
565
565
|
throw new Error(
|
|
566
|
-
`Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL
|
|
566
|
+
`Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL.`
|
|
567
567
|
);
|
|
568
568
|
}
|
|
569
|
-
|
|
569
|
+
let path = parsedUrl.pathname.replace(/\/+$/, "") || "/";
|
|
570
|
+
if (/\/chat\/completions$/.test(path)) {
|
|
571
|
+
} else if (path === "/") {
|
|
572
|
+
path = "/v1/chat/completions";
|
|
573
|
+
} else if (/\/v\d+$/.test(path)) {
|
|
574
|
+
path = `${path}/chat/completions`;
|
|
575
|
+
}
|
|
576
|
+
parsedUrl.pathname = path;
|
|
570
577
|
return parsedUrl.toString();
|
|
571
578
|
}
|
|
572
579
|
|