magicrew-auth 0.3.23 → 0.3.24
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/AGENTS.md +1 -0
- package/CHANGELOG.md +48 -0
- package/docs/internals.md +51 -4
- package/package.json +1 -1
- package/src/hooks/loader.mjs +60 -4
package/AGENTS.md
CHANGED
|
@@ -67,6 +67,7 @@ curl -s "https://usemagpie.ai/api/latest?channel=stable" | grep -o '"version":"[
|
|
|
67
67
|
| 14 | **卡片上额度正常,路由页里这个号却是「剩余额度未知」** | 窗口**不能标 `aside`**:magpie 的 `allowanceOf()` 把 `Aside`/`Text` 的窗口整条跳过,一条不剩 ⇒ 该号 `Known=false`(官网文档 issue #3650 明写)。判据:`curl -s http://127.0.0.1:3425/v1/magpie/trace`,看这个 provider 的 `known` 是 `true` 还是**字段缺失** | HOOKS 三 · `src/hooks/usage.mjs` 头 |
|
|
68
68
|
| 15 | 接 ZCode / Claude Code 系客户端,带工具的请求回 `400 {"error":{"message":"模型响应失败","code":4017}}` | **上游的 `additionalProperties` 只认布尔值,任何对象形态都拒** —— 不只是 `{}`,`{type:"string"}` 这种有约束的 schema 一样 400(两个模型逐行一致)。判据:同一请求只把该字段换成 `true` → 200。改写已在 `rewriteRequestBody` 里做(非布尔一律换 true;**这是降级放宽、会丢约束**,取舍见 internals 十七) | internals 十七 · `tests/body-rewrite.mjs` |
|
|
69
69
|
| 16 | 网关路径(agent 从 magpie 的 `/v1` 进来)报 `502 did not stream: token无效`,而 provider 显示已登录 | auth 里没带 org → `accountOf()` 落 `"default"` → 读不到真实账号的 key 槽 → 只能用 `auth.access`,它过了 2 小时就 403。`accountFor()` 已在**恰好一个真实账号**时兜到它;**多账号不猜**(猜错就是拿别人的 key 扣别人的积分) | internals 十四 · 十五 · `tests/account-fallback.mjs` |
|
|
70
|
+
| 17 | 单轮回 `400 ... tools.N.function.parameters/properties/functions/items: invalid JSON Schema` | 上游不接受**空 schema 对象**当 `items`:`items:{}` 必 400,`items:{description:"x"}` 却 200。判据:同一请求只删 `items:{}` → 200。改写已做(删 `items:{}` / `true`,**严格等价**;`false` 不动) | internals 十七 · `tests/body-rewrite.mjs` |
|
|
70
71
|
|
|
71
72
|
**坑 14 的两个附带判据**(都实测过):
|
|
72
73
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.3.24 — 2026-10-28
|
|
4
|
+
|
|
5
|
+
**修 `items: {}` 导致的整轮 400**
|
|
6
|
+
|
|
7
|
+
### 症状
|
|
8
|
+
|
|
9
|
+
用户的一次普通对话直接失败,界面只显示:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
本轮运行失败 400: {"code":null,"message":"超级麦吉: HTTP 400 请求错误:
|
|
13
|
+
{\"error\":{\"code\":\"InvalidParameter\",\"message\":\"tools.40.function.parameters/
|
|
14
|
+
properties/functions/items: invalid JSON Schema; check the schema keywords
|
|
15
|
+
and references Request id: ...\",\"param\":\"\",\"type\":\"BadRequest\"}}",
|
|
16
|
+
"param":null,"type":"invalid_request_error"}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 根因
|
|
20
|
+
|
|
21
|
+
某个工具的参数 schema 里,数组的 `items` 是**空对象 `{}`**。上游的 JSON Schema
|
|
22
|
+
校验器只拒这一种空 schema 形态;`items:{description:"x"}`、`items:{type:"string"}`、
|
|
23
|
+
缺省 `items` 都是 200。国内站 `magicrew` 与国际站 `magicrew-intl` 的报错逐行一致。
|
|
24
|
+
|
|
25
|
+
按 JSON Schema 规范 `items:{}` 是合法的("任意元素"),客户端没写错;这是上游
|
|
26
|
+
兼容性不足。但插件是唯一能改请求体的地方。
|
|
27
|
+
|
|
28
|
+
### 修法
|
|
29
|
+
|
|
30
|
+
`src/hooks/loader.mjs` 新增 `loosenEmptyItems()`,在 `rewriteRequestBody` 里递归扫
|
|
31
|
+
`tools[].function.parameters`:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
items: {} → 删除 // JSON Schema 缺省值就是 {},严格等价
|
|
35
|
+
items: true → 删除 // 同上;上游对它回的是另一种 400
|
|
36
|
+
items: false → 不动 // 「数组必须为空」,删掉就是放开
|
|
37
|
+
items: {...} → 不动 // 有任何一个键,上游都收
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
与 `additionalProperties`、`max_tokens`、`stream_options` 共用一次 `JSON.parse`;
|
|
41
|
+
没改到时仍返回原始字符串。
|
|
42
|
+
|
|
43
|
+
### 验证
|
|
44
|
+
|
|
45
|
+
- `tests/body-rewrite.mjs`:31 个断言(新增 11 个),`items:{}` / `true` 删除、
|
|
46
|
+
`{description}` / `{type}` / `false` 不动、`tools[40]` 命中、combined 改写、不误伤。
|
|
47
|
+
- 端到端(2026-10-28):41 个工具、第 41 个带 `properties.functions.items:{}`,
|
|
48
|
+
原样请求 → `400 tools.40.../items`;过 `rewriteRequestBody` → `200`。
|
|
49
|
+
国内站、国际站都跑过。
|
|
50
|
+
|
|
3
51
|
## 0.3.23 — 2026-10-28
|
|
4
52
|
|
|
5
53
|
**修工具 schema 兼容 + 网关路径拿错 key,顺手把「签一把顶一把」测清楚了**
|
package/docs/internals.md
CHANGED
|
@@ -1055,7 +1055,12 @@ magicrew 既不返回 `maxInputTokens`(`flows/models` 的 `configs` 只有 tem
|
|
|
1055
1055
|
|
|
1056
1056
|
---
|
|
1057
1057
|
|
|
1058
|
-
##
|
|
1058
|
+
## 十七、上游不认空 schema —— `additionalProperties: {}` 与 `items: {}`
|
|
1059
|
+
|
|
1060
|
+
> 两处是同一类兼容性问题的两个面:客户端按 JSON Schema 规范写出**空对象**,
|
|
1061
|
+
> 上游的校验器/适配层拒收。都只能在请求改写里兜。
|
|
1062
|
+
|
|
1063
|
+
### 一、`additionalProperties: {}` → `400 code 4017`
|
|
1059
1064
|
|
|
1060
1065
|
**症状**:接 ZCode / Claude Code 系客户端时,一次带 54 个工具定义的请求必定失败:
|
|
1061
1066
|
|
|
@@ -1111,19 +1116,61 @@ POST /v1/chat/completions
|
|
|
1111
1116
|
保留原值就是整个请求失败。两害相权,让工具调用能跑(可能多出未声明的字段)
|
|
1112
1117
|
比让 agent 完全不可用要好;方向也安全 —— 是放宽不是收紧,不会让原本合法的调用被拒。
|
|
1113
1118
|
|
|
1114
|
-
它与 `max_tokens`、`stream_options`
|
|
1119
|
+
它与 `max_tokens`、`stream_options`、`items: {}` 四处改写共用**一次** `JSON.parse`;
|
|
1115
1120
|
没改到时返回原始字符串(省一次序列化 —— 54 工具的请求体可达上百 KB)。
|
|
1116
1121
|
|
|
1117
|
-
边界由 `tests/body-rewrite.mjs` 钉住(
|
|
1122
|
+
边界由 `tests/body-rewrite.mjs` 钉住(31 个断言):非布尔一律换、布尔一律不动、
|
|
1118
1123
|
其余字段一字不动、没改到就不重新序列化。
|
|
1119
1124
|
|
|
1125
|
+
### 二、`items: {}` → `400 invalid JSON Schema`
|
|
1126
|
+
|
|
1127
|
+
**症状**(2026-10-28,用户截图原文):
|
|
1128
|
+
|
|
1129
|
+
```
|
|
1130
|
+
HTTP 400
|
|
1131
|
+
{"code":null,"message":"超级麦吉: HTTP 400 请求错误: {\"error\":{\"code\":\"InvalidParameter\",
|
|
1132
|
+
\"message\":\"tools.40.function.parameters/properties/functions/items: invalid JSON Schema;
|
|
1133
|
+
check the schema keywords and references Request id: ...\",\"param\":\"\",\"type\":\"BadRequest\"}}",
|
|
1134
|
+
"param":null,"type":"invalid_request_error"}
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
与 `additionalProperties` 那条不同:这次上游**给了完整路径**(`tools.40.../items`),
|
|
1138
|
+
而且国内站与 `magicrew-intl` 的报错逐行一致。
|
|
1139
|
+
|
|
1140
|
+
**最小复现**:41 个工具,第 41 个的参数是
|
|
1141
|
+
|
|
1142
|
+
```json
|
|
1143
|
+
{"type":"object","properties":{"functions":{"type":"array","items":{}}},"required":["functions"]}
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
| `items` | 结果 |
|
|
1147
|
+
|---|---|
|
|
1148
|
+
| 缺省 | 200 |
|
|
1149
|
+
| `{}` | **400 invalid JSON Schema**(路径 `.../properties/functions/items`) |
|
|
1150
|
+
| `{description:"x"}` | 200 —— 空对象才炸,不是"缺 type" |
|
|
1151
|
+
| `{type:"string"}` | 200 |
|
|
1152
|
+
| `true` | 400 `模型响应失败`(另一条 400) |
|
|
1153
|
+
| `false` | 400 `模型响应失败` |
|
|
1154
|
+
|
|
1155
|
+
**根因**:客户端用 `z.array(z.unknown())` / 动态函数列表生成 schema 时,数组元素序列化成
|
|
1156
|
+
`{}`。JSON Schema 里 `items` 的缺省值本来就是 `{}` —— "任意元素",所以
|
|
1157
|
+
`items: {}` 删掉是**严格等价**,不是降级放宽;`items: true` 同理。
|
|
1158
|
+
`items: false`("数组必须为空")语义不同,不能删,原样留着。
|
|
1159
|
+
|
|
1160
|
+
**修法**:`loosenEmptyItems()` 递归删 `items: {}` 与 `items: true`,与
|
|
1161
|
+
`loosenAdditionalProperties()` 在同一个 `rewriteRequestBody` 里各跑一遍。
|
|
1162
|
+
|
|
1163
|
+
**端到端验证**(2026-10-28):同一份 41 工具请求
|
|
1164
|
+
`raw → 400 tools.40.../items`;过 `rewriteRequestBody` 后 `→ 200`。
|
|
1165
|
+
国内站、国际站都验过。
|
|
1166
|
+
|
|
1120
1167
|
### 为什么算插件的事,不算客户端的事
|
|
1121
1168
|
|
|
1122
1169
|
按规范 `{}` 与 `true` 等价,客户端没写错;**上游的兼容性不足**。
|
|
1123
1170
|
但插件是唯一能改请求体的地方(见 `src/hooks/loader.mjs` 顶部),
|
|
1124
1171
|
在这里兜住比让每个用户去改 ZCode 的源码现实。
|
|
1125
1172
|
|
|
1126
|
-
###
|
|
1173
|
+
### 同一处这些改写的两个教训
|
|
1127
1174
|
|
|
1128
1175
|
同一次排查里还确认了两件事,都值得记住:
|
|
1129
1176
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magicrew-auth",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.24",
|
|
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
|
@@ -67,9 +67,61 @@ export function loosenAdditionalProperties(node) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
70
|
+
* 上游的 JSON Schema 校验器不接受 `items: {}`,整轮请求回 HTTP 400:
|
|
71
71
|
*
|
|
72
|
-
*
|
|
72
|
+
* {"error":{"code":null,"message":"超级麦吉: HTTP 400 请求错误: {\"error\":
|
|
73
|
+
* {\"code\":\"InvalidParameter\",\"message\":\"tools.40.function.parameters/
|
|
74
|
+
* properties/functions/items: invalid JSON Schema; check the schema
|
|
75
|
+
* keywords and references Request id: ...\",\"param\":\"\",\"type\":
|
|
76
|
+
* \"BadRequest\"}}","param":null,"type":"invalid_request_error"}}
|
|
77
|
+
*
|
|
78
|
+
* 报错只给路径,不说是哪个客户端。实测(2026-10-28,同一把 key、同一份请求、
|
|
79
|
+
* 只改 tools[0] 的这一处;国内站 `magicrew` 与国际站 `magicrew-intl` 逐行一致):
|
|
80
|
+
*
|
|
81
|
+
* items: 缺省 → 200
|
|
82
|
+
* items: {} → 400 invalid JSON Schema
|
|
83
|
+
* items: {description:"x"} → 200 // 空对象才炸,不是"缺 type"
|
|
84
|
+
* items: {type:"string"} → 200
|
|
85
|
+
* items: true → 400 模型响应失败(另一种 400)
|
|
86
|
+
* items: false → 400 模型响应失败
|
|
87
|
+
*
|
|
88
|
+
* 为什么可以删:JSON Schema 里 `items` 的缺省值就是 `{}` —— "任意元素"。
|
|
89
|
+
* 所以 `items: {}` 删掉是**严格等价**(`true` 同理),不是降级放宽。
|
|
90
|
+
* `items: false` 表示"数组必须为空",语义不同,不能删,原样留着。
|
|
91
|
+
*
|
|
92
|
+
* 命中来源:客户端用 `z.array(z.unknown())` / 动态函数列表生成工具 schema 时,
|
|
93
|
+
* 数组元素会序列化成 `{}`。一个真实请求里第 41 个工具的参数是
|
|
94
|
+
* `properties.functions.items: {}`,于是整轮失败。
|
|
95
|
+
*
|
|
96
|
+
* @returns {boolean} 有没有改到
|
|
97
|
+
*/
|
|
98
|
+
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
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 改写发往上游的请求体。**一次 parse,四处改写都在这**。
|
|
123
|
+
*
|
|
124
|
+
* 四处都是实测出来的,不是猜的:
|
|
73
125
|
*
|
|
74
126
|
* ① max_tokens 抬到 512
|
|
75
127
|
* 实测 glm-5.3-flash:64 → finish_reason:"length" 且 content:""
|
|
@@ -83,6 +135,8 @@ export function loosenAdditionalProperties(node) {
|
|
|
83
135
|
*
|
|
84
136
|
* ③ tools 里的 additionalProperties: {} → true(见 looser 的注释)
|
|
85
137
|
*
|
|
138
|
+
* ④ tools 里的 items: {} → 删除(见 loosenEmptyItems 的注释)
|
|
139
|
+
*
|
|
86
140
|
* 没改到时返回原始字符串(省一次序列化 —— 54 个工具的请求体不小)。
|
|
87
141
|
*
|
|
88
142
|
* @param {unknown} raw
|
|
@@ -116,7 +170,9 @@ export function rewriteRequestBody(raw) {
|
|
|
116
170
|
if (Array.isArray(j.tools)) {
|
|
117
171
|
for (const t of j.tools) {
|
|
118
172
|
const p = t?.function?.parameters
|
|
119
|
-
if (p
|
|
173
|
+
if (!p || typeof p !== "object") continue
|
|
174
|
+
if (loosenAdditionalProperties(p)) changed = true
|
|
175
|
+
if (loosenEmptyItems(p)) changed = true
|
|
120
176
|
}
|
|
121
177
|
}
|
|
122
178
|
|
|
@@ -163,7 +219,7 @@ export function makeLoader(c) {
|
|
|
163
219
|
let body = init?.body
|
|
164
220
|
if (body === undefined && req) body = await req.text()
|
|
165
221
|
|
|
166
|
-
//
|
|
222
|
+
// 四处请求体改写,全在 rewriteRequestBody 里(那里有实测出处)
|
|
167
223
|
body = rewriteRequestBody(body)
|
|
168
224
|
|
|
169
225
|
return fetch(url, {
|