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 +24 -0
- package/docs/internals.md +7 -2
- package/package.json +1 -1
- package/src/hooks/loader.mjs +79 -35
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` 钉住(
|
|
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()`
|
|
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.
|
|
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",
|
package/src/hooks/loader.mjs
CHANGED
|
@@ -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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 (
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
/**
|