@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 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 full chat endpoint URL (for example: https://api.openai.com/v1/chat/completions)."
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 that points to a full chat endpoint.`
566
+ `Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL.`
567
567
  );
568
568
  }
569
- parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, "") || "/";
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 with minimal, non-breaking rules:
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
- * Note: This function does NOT auto-append /v1 or /chat/completions.
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 full chat endpoint URL (for example: https://api.openai.com/v1/chat/completions).'
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
- parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, '') || '/';
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 with minimal, non-breaking rules:
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
- * Note: This function does NOT auto-append /v1 or /chat/completions.
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 full chat endpoint URL (for example: https://api.openai.com/v1/chat/completions).'
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
- parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, '') || '/';
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 full chat endpoint URL (for example: https://api.openai.com/v1/chat/completions)."
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 that points to a full chat endpoint.`
566
+ `Invalid apiUrl: "${apiUrl}". Please provide a valid absolute URL.`
567
567
  );
568
568
  }
569
- parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, "") || "/";
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-server",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "ReiStandard Active Messaging server SDK with pluggable database adapters",
5
5
  "repository": {
6
6
  "type": "git",