magicrew-auth 0.3.24 → 0.3.25

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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.3.25 — 2026-10-28
4
+
5
+ **0.3.24 的 schema 扫描范围收窄:只走 schema 位置**
6
+
7
+ 0.3.24 的 `loosenEmptyItems()` 与 `loosenAdditionalProperties()` 递归了**所有**对象。
8
+ 那会把 `properties` 里恰好叫 `items` / `additionalProperties` 的工具参数当成 schema
9
+ 关键字:`properties.items = {}` 的参数会被删掉,`properties.additionalProperties =
10
+ {type:"string"}` 会被改成 `true`;`enum` / `default` 值里的同名键也会被误改。
11
+
12
+ 两者现在共用 `walkSchema()`,只进入真正的 schema 位置:
13
+
14
+ - `properties` / `patternProperties` / `$defs` / `definitions` / `dependentSchemas` 的**值**
15
+ - `allOf` / `anyOf` / `oneOf` / `prefixItems`
16
+ - `items` / `additionalProperties` / `unevaluatedProperties` / `propertyNames` /
17
+ `contains` / `not` / `if` / `then` / `else` / `contentSchema` / `dependencies`
18
+ - 不扫键名,不扫 `enum` / `default` / `const` / `examples`(它们不是 schema)
19
+
20
+ 0.3.24 的 `items:{}` 等价替换规则不变,本版只修扫描范围。
21
+
22
+ ### 验证
23
+
24
+ - `tests/body-rewrite.mjs`:36 个断言(新增 5 个同名属性 / `enum` 值反例)。
25
+ - 端到端复跑:41 个工具、`properties.functions.items:{}` → 200(国内 / 国际)。
26
+
3
27
  ## 0.3.24 — 2026-10-28
4
28
 
5
29
  **修 `items: {}` 导致的整轮 400**
package/docs/internals.md CHANGED
@@ -1119,7 +1119,7 @@ POST /v1/chat/completions
1119
1119
  它与 `max_tokens`、`stream_options`、`items: {}` 四处改写共用**一次** `JSON.parse`;
1120
1120
  没改到时返回原始字符串(省一次序列化 —— 54 工具的请求体可达上百 KB)。
1121
1121
 
1122
- 边界由 `tests/body-rewrite.mjs` 钉住(31 个断言):非布尔一律换、布尔一律不动、
1122
+ 边界由 `tests/body-rewrite.mjs` 钉住(36 个断言):非布尔一律换、布尔一律不动、
1123
1123
  其余字段一字不动、没改到就不重新序列化。
1124
1124
 
1125
1125
  ### 二、`items: {}` → `400 invalid JSON Schema`
@@ -1157,9 +1157,14 @@ HTTP 400
1157
1157
  `items: {}` 删掉是**严格等价**,不是降级放宽;`items: true` 同理。
1158
1158
  `items: false`("数组必须为空")语义不同,不能删,原样留着。
1159
1159
 
1160
- **修法**:`loosenEmptyItems()` 递归删 `items: {}` 与 `items: true`,与
1160
+ **修法**:`loosenEmptyItems()` 删 `items: {}` 与 `items: true`,与
1161
1161
  `loosenAdditionalProperties()` 在同一个 `rewriteRequestBody` 里各跑一遍。
1162
1162
 
1163
+ 两者共用 `walkSchema()`,**只进入真正的 schema 位置**(`properties` 等映射的**值**、
1164
+ `allOf` / `anyOf` / `oneOf`、`items` / `additionalProperties` / `not` / `if` 等),
1165
+ 不扫键名,也不扫 `enum` / `default` / `const`。否则一个恰好叫 `items` 或
1166
+ `additionalProperties` 的工具参数会被当成关键字误删/误改(0.3.24 的教训,0.3.25 修)。
1167
+
1163
1168
  **端到端验证**(2026-10-28):同一份 41 工具请求
1164
1169
  `raw → 400 tools.40.../items`;过 `rewriteRequestBody` 后 `→ 200`。
1165
1170
  国内站、国际站都验过。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magicrew-auth",
3
- "version": "0.3.24",
3
+ "version": "0.3.25",
4
4
  "description": "超级麦吉 / MagiCrew for magpie and OpenCode — one package, two providers: letsmagic.cn (国内) and magicrew.ai (international). Password or web-JWT sign-in, the account's own live model list, points balance, newcomer rewards, invite codes.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
@@ -42,30 +42,82 @@ import { ensureKey, accountOf } from "../keys.mjs"
42
42
  * 一次真实请求里 54 个工具中的 `ExitPlanMode` 与 `CreateWorkflow` 就这么写。
43
43
  * 客户端没写错(按规范 `{}` 与 `true` 等价),是上游兼容性不足,插件这边兜住。
44
44
  *
45
+ * 只走 schema 位置(见 `walkSchema`):一个恰好叫 `additionalProperties` 的工具参数
46
+ * 不会被误改。
47
+ *
45
48
  * @returns {boolean} 有没有改到
46
49
  */
47
- export function loosenAdditionalProperties(node) {
48
- if (!node || typeof node !== "object") return false
49
- if (Array.isArray(node)) {
50
- let any = false
51
- for (const n of node) if (loosenAdditionalProperties(n)) any = true
52
- return any
53
- }
54
- let changed = false
50
+ // JSON Schema 里"值是 schema"的关键字。只走这些位置,**不扫 properties 的键名**,
51
+ // 也不扫 enum / default / const / examples —— 那些不是 schema。
52
+ // 旧写法递归所有对象,会把一个叫 `additionalProperties` 的工具参数(在
53
+ // properties 的键上)当成关键字改掉,也会把 enum 值里的 `items` 键删掉。
54
+ const SCHEMA_MAPS = new Set([
55
+ "properties",
56
+ "patternProperties",
57
+ "$defs",
58
+ "definitions",
59
+ "dependentSchemas",
60
+ ])
61
+ const SCHEMA_ONE = new Set([
62
+ "additionalProperties",
63
+ "unevaluatedProperties",
64
+ "propertyNames",
65
+ "contains",
66
+ "not",
67
+ "if",
68
+ "then",
69
+ "else",
70
+ "contentSchema",
71
+ "items",
72
+ "additionalItems",
73
+ "unevaluatedItems",
74
+ ])
75
+ const SCHEMA_LIST = new Set(["allOf", "anyOf", "oneOf", "prefixItems"])
76
+
77
+ /**
78
+ * 在 schema 对象上原地跑 `visit`,并只递归进真正的 schema 位置。
79
+ * @returns {boolean} `visit` 或任一子 schema 改到过
80
+ */
81
+ function walkSchema(node, visit) {
82
+ if (!node || typeof node !== "object" || Array.isArray(node)) return false
83
+ let changed = visit(node)
55
84
  for (const [k, v] of Object.entries(node)) {
56
- if (k === "additionalProperties") {
57
- // 布尔值上游认,原样留着(false 是"不许额外属性",语义相反,绝对不能动)
58
- if (typeof v !== "boolean") {
59
- node[k] = true
60
- changed = true
61
- }
85
+ if (!v || typeof v !== "object") continue
86
+ if (SCHEMA_MAPS.has(k) && !Array.isArray(v)) {
87
+ for (const child of Object.values(v)) if (walkSchema(child, visit)) changed = true
88
+ continue
89
+ }
90
+ if (SCHEMA_LIST.has(k) && Array.isArray(v)) {
91
+ for (const child of v) if (walkSchema(child, visit)) changed = true
62
92
  continue
63
93
  }
64
- if (v && typeof v === "object" && loosenAdditionalProperties(v)) changed = true
94
+ if (SCHEMA_ONE.has(k)) {
95
+ if (Array.isArray(v)) {
96
+ for (const child of v) if (walkSchema(child, visit)) changed = true
97
+ } else if (walkSchema(v, visit)) changed = true
98
+ continue
99
+ }
100
+ if (k === "dependencies" && !Array.isArray(v)) {
101
+ for (const child of Object.values(v)) {
102
+ if (child && typeof child === "object" && !Array.isArray(child) && walkSchema(child, visit)) {
103
+ changed = true
104
+ }
105
+ }
106
+ }
65
107
  }
66
108
  return changed
67
109
  }
68
110
 
111
+ export function loosenAdditionalProperties(node) {
112
+ return walkSchema(node, (n) => {
113
+ // 布尔值上游认,原样留着(false 是"不许额外属性",语义相反,绝对不能动)
114
+ if (!Object.hasOwn(n, "additionalProperties")) return false
115
+ if (typeof n.additionalProperties === "boolean") return false
116
+ n.additionalProperties = true
117
+ return true
118
+ })
119
+ }
120
+
69
121
  /**
70
122
  * 上游的 JSON Schema 校验器不接受 `items: {}`,整轮请求回 HTTP 400:
71
123
  *
@@ -93,29 +145,21 @@ export function loosenAdditionalProperties(node) {
93
145
  * 数组元素会序列化成 `{}`。一个真实请求里第 41 个工具的参数是
94
146
  * `properties.functions.items: {}`,于是整轮失败。
95
147
  *
148
+ * 只走 schema 位置:`properties` 里一个恰好叫 `items` 的工具参数(哪怕 schema 就是
149
+ * `{}`)不会被误删;`enum` / `default` 值里的 `items` 键同样不碰。
150
+ *
96
151
  * @returns {boolean} 有没有改到
97
152
  */
98
153
  export function loosenEmptyItems(node) {
99
- if (!node || typeof node !== "object") return false
100
- if (Array.isArray(node)) {
101
- let any = false
102
- for (const n of node) if (loosenEmptyItems(n)) any = true
103
- return any
104
- }
105
- let changed = false
106
- for (const [k, v] of Object.entries(node)) {
107
- if (k === "items") {
108
- const emptyObject =
109
- v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0
110
- if (emptyObject || v === true) {
111
- delete node[k]
112
- changed = true
113
- continue
114
- }
115
- }
116
- if (v && typeof v === "object" && loosenEmptyItems(v)) changed = true
117
- }
118
- return changed
154
+ return walkSchema(node, (n) => {
155
+ if (!Object.hasOwn(n, "items")) return false
156
+ const v = n.items
157
+ const emptyObject =
158
+ v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0
159
+ if (!emptyObject && v !== true) return false
160
+ delete n.items
161
+ return true
162
+ })
119
163
  }
120
164
 
121
165
  /**