magicrew-auth 0.3.7 → 0.3.13

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/src/context.mjs CHANGED
@@ -1,60 +1,78 @@
1
- // 站点上下文 —— 每个供应商拿到的那一份「我是谁」
2
- //
3
- // 一个包两个供应商,所以这里不是常量,是**按站点生成**的:
4
- //
5
- // siteCtx(SITES.cn) → magicrew 超级麦吉
6
- // siteCtx(SITES.intl) → magicrew-intl MagiCrew
7
- //
8
- // 所有小插头(hooks/*)拿到的都是这个对象,它们不自己去认站点。
9
-
10
- import { SITES } from "./api.mjs"
11
- import { ICON_BY_SITE } from "./icons.mjs"
12
- import { contextOf } from "./model-context.mjs"
13
-
14
- export { SITES }
15
-
16
- /** 决定 API 形状,别乱选(见 src/api.mjs 的 base 与 hooks/models 的 api 字段) */
17
- export const SDK = "@ai-sdk/openai-compatible"
18
-
19
- /** api_key 只活 2 小时时,提前多久换 */
20
- export const REFRESH_LEAD_MS = 10 * 60_000
21
-
22
- /**
23
- * 站点 → 上下文。
24
- *
25
- * 除了 SITES 里那些(id / host / web / needsOrgOnGateway),再补三个派生值:
26
- * SHORT 卡片上显示的短名
27
- * BASE 模型网关 <host>/v1
28
- * ICON 这个供应商自己的图标(插件卡片的图标另外在 package.json.magpie.icon)
29
- */
30
- export function siteCtx(SITE) {
31
- return {
32
- ...SITE,
33
- PROVIDER: SITE.id,
34
- SHORT: SITE.id === "magicrew" ? "超级麦吉" : "MagiCrew",
35
- BASE: `${SITE.host}/v1`,
36
- ICON: SITE.id === "magicrew" ? ICON_BY_SITE.cn : ICON_BY_SITE.intl,
37
- }
38
- }
39
-
40
- /** 把一条 flows/models 变成一个 magpie 模型条目 */
41
- export function modelEntry(c, id, name, vision) {
42
- return {
43
- id,
44
- name,
45
- api: { id, url: c.BASE, npm: SDK },
46
- status: "active",
47
- cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
48
- // 上下文窗口向 magpie 的模型库查 —— 两个模型接口都不返回它,
49
- // 早前一律填 256K,结果 agent 拿 256K 判断"放不下",而实际是 1M
50
- limit: { context: contextOf(id), output: 64_000 },
51
- capabilities: {
52
- temperature: true,
53
- reasoning: true,
54
- toolcall: true,
55
- attachment: !!vision,
56
- input: { text: true, image: !!vision },
57
- output: { text: true },
58
- },
59
- }
60
- }
1
+ // 站点上下文 —— 每个供应商拿到的那一份「我是谁」
2
+ //
3
+ // 一个包两个供应商,所以这里不是常量,是**按站点生成**的:
4
+ //
5
+ // siteCtx(SITES.cn) → magicrew 超级麦吉
6
+ // siteCtx(SITES.intl) → magicrew-intl MagiCrew
7
+ //
8
+ // 所有小插头(hooks/*)拿到的都是这个对象,它们不自己去认站点。
9
+
10
+ import { SITES } from "./api.mjs"
11
+ import { ICON_BY_SITE } from "./icons.mjs"
12
+ import { contextOf } from "./model-context.mjs"
13
+
14
+ export { SITES }
15
+
16
+ /** 决定 API 形状,别乱选(见 src/api.mjs 的 base 与 hooks/models 的 api 字段) */
17
+ export const SDK = "@ai-sdk/openai-compatible"
18
+
19
+ /** api_key 只活 2 小时时,提前多久换 */
20
+ export const REFRESH_LEAD_MS = 10 * 60_000
21
+
22
+ /**
23
+ * 站点 → 上下文。
24
+ *
25
+ * 除了 SITES 里那些(id / host / web / needsOrgOnGateway),再补三个派生值:
26
+ * SHORT 卡片上显示的短名
27
+ * BASE 模型网关 <host>/v1
28
+ * ICON 这个供应商自己的图标(插件卡片的图标另外在 package.json.magpie.icon)
29
+ */
30
+ export function siteCtx(SITE) {
31
+ return {
32
+ ...SITE,
33
+ PROVIDER: SITE.id,
34
+ SHORT: SITE.id === "magicrew" ? "超级麦吉" : "MagiCrew",
35
+ BASE: `${SITE.host}/v1`,
36
+ ICON: SITE.id === "magicrew" ? ICON_BY_SITE.cn : ICON_BY_SITE.intl,
37
+ }
38
+ }
39
+
40
+ /**
41
+ * 把一条 flows/models 变成一个 magpie 模型条目。
42
+ *
43
+ * `opts.rate` / `opts.free` 是 **magpie 自己的字段**(从 codearts 插件里挖到的,
44
+ * packages/codearts/index.mjs 的 modelOf):
45
+ *
46
+ * free boolean → 名字后面画一个绿色的「免费」胶囊
47
+ * rate number → 画一个灰色的倍率胶囊(0.09× 这种,就是网站上的「消耗系数」)
48
+ *
49
+ * 关键规则(codearts 原注释):**free 的时候不给 rate** ——
50
+ * 免费模型不该再标一个倍率,两个胶囊并排没意义。
51
+ * 没有 rate 就什么都不画,正好符合「没有倍率就不显示」。
52
+ *
53
+ * ⚠ 别再用 name 拼倍率了:那样筛选框会连倍率一起匹配,
54
+ * 而且和真正的胶囊样式差得远。
55
+ */
56
+ export function modelEntry(c, id, name, vision, opts = {}) {
57
+ const { rate = 0, free = false } = opts
58
+ return {
59
+ id,
60
+ name,
61
+ api: { id, url: c.BASE, npm: SDK },
62
+ status: "active",
63
+ cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
64
+ // 上下文窗口向 magpie 的模型库查 —— 两个模型接口都不返回它,
65
+ // 早前一律填 256K,结果 agent 拿 256K 判断"放不下",而实际是 1M
66
+ limit: { context: contextOf(id), output: 64_000 },
67
+ capabilities: {
68
+ temperature: true,
69
+ reasoning: true,
70
+ toolcall: true,
71
+ attachment: !!vision,
72
+ input: { text: true, image: !!vision },
73
+ output: { text: true },
74
+ },
75
+ ...(free ? { free: true } : {}),
76
+ ...(!free && Number(rate) > 0 ? { rate: Number(rate) } : {}),
77
+ }
78
+ }
@@ -7,11 +7,18 @@
7
7
  //
8
8
  // 签到逻辑在独立模块里**按需加载**:它挂了,登录/模型/额度照常工作。
9
9
 
10
+ import { rollInvite } from "../store.mjs"
11
+
10
12
  export function makeCheckin(c) {
11
13
  return async (getAuth) => {
12
14
  const auth = await getAuth()
13
15
  if (!auth) return { outcome: "failed", message: "未登录" }
14
16
  try {
17
+ // 每天顺带把邀请码滚一次。
18
+ // 登录那一路虽然已经改成 await,但万一 host 进程在中途被回收,
19
+ // 滚动仍会丢;挂在这里(magpie 保证每天跑一次)可以第二天补上。
20
+ // rollInvite 内部自带超时和 try/catch,失败不影响签到。
21
+ await rollInvite(c.id, auth.jwt, auth.org, c.host)
15
22
  const { runCheckin } = await import("../tasks.mjs")
16
23
  return await runCheckin({ ...auth, host: c.host })
17
24
  } catch (e) {
@@ -1,93 +1,93 @@
1
- // ④ auth.loader(getAuth) —— 请求接管
2
- //
3
- // 返回 baseURL / apiKey / headers / fetch(a,b),magpie 拿它发真实请求。
4
- // 这是唯一能改请求体的地方。本项目用它干两件事:
5
- //
6
- // · 国际站网关强制要 organization-code(国内站不要),不带就是
7
- // HTTP 500 "Internal Server Error."(22 字节纯文本,不是 JSON)
8
- // · 推理模型低于 512 的 max_tokens 会被 reasoning 吃光,content 返回空串
9
- //
10
- // ⚠ 不要在这里读流、不要重试换号、不要手拼 x-api-key ——
11
- // 前两件 magpie 做了,第三件看 SDK 包自动加。见 README「最小交付面」。
12
-
13
- import { ensureKey, accountOf } from "../keys.mjs"
14
-
15
- export function makeLoader(c) {
16
- return async (getAuth) => {
17
- const auth = await getAuth()
18
- if (!auth) return {}
19
- return {
20
- baseURL: c.BASE,
21
- apiKey: auth.access,
22
- headers: { "user-agent": "MagicrewClient/1.0" },
23
- async fetch(input, init) {
24
- const fresh = await getAuth()
25
- if (!fresh) throw new Error(`${c.SHORT} 未登录`)
26
-
27
- // key 只有一个来源(src/keys.mjs)—— 服务端签一把顶一把,多入口必互相吊销
28
- const key = await ensureKey(c, getAuth)
29
- if (!key) throw new Error(`${c.SHORT} 未登录(拿不到可用的 key)`)
30
- const account = accountOf(fresh)
31
-
32
- const req = input instanceof Request ? input : null
33
- const url =
34
- typeof input === "string"
35
- ? input
36
- : input instanceof URL
37
- ? input.toString()
38
- : (req?.url ?? "")
39
-
40
- const incoming = new Headers(init?.headers ?? req?.headers ?? {})
41
- const headers = {
42
- authorization: `Bearer ${key.apiKey}`,
43
- "content-type": "application/json",
44
- }
45
- if (c.needsOrgOnGateway && fresh.org) headers["organization-code"] = fresh.org
46
-
47
- for (const [k, v] of incoming) {
48
- if (["authorization", "host", "content-length", "user-agent"].includes(k.toLowerCase())) continue
49
- headers[k] = v
50
- }
51
-
52
- let body = init?.body
53
- if (body === undefined && req) body = await req.text()
54
-
55
- // 改写两处请求体,都为实测出来的:
56
- //
57
- // ① max_tokens 抬到 512
58
- // 实测 glm-5.3-flash:64 → finish_reason:"length" 且 content:""
59
- // 512 → 正常出话(reasoning 208 / total 233)
60
- //
61
- // ② 流式请求补 stream_options.include_usage
62
- // magicrew 在 stream:true 下**默认不返回 usage**;补上之后,[DONE]
63
- // 之前会多一条 {"choices":[],"usage":{...}} 的收尾 chunk。
64
- // magpie 新版靠这条收尾 chunk 判定「流正常结束了」——缺了它就报
65
- // 「Stream ended without finish_reason」然后重试(实测 2026-10-27)。
66
- // 控制台里看到的就是:模型请求重试 (3/5) · Stream ended without finish_reason
67
- if (typeof body === "string" && body.includes('"stream"')) {
68
- try {
69
- const j = JSON.parse(body)
70
- if (Number(j.max_tokens) > 0 && Number(j.max_tokens) < 512) j.max_tokens = 512
71
- if (j.stream === true && !j.stream_options) {
72
- j.stream_options = { include_usage: true }
73
- }
74
- body = JSON.stringify(j)
75
- } catch {}
76
- } else if (typeof body === "string" && body.includes('"max_tokens"')) {
77
- try {
78
- const j = JSON.parse(body)
79
- if (Number(j.max_tokens) > 0 && Number(j.max_tokens) < 512) j.max_tokens = 512
80
- body = JSON.stringify(j)
81
- } catch {}
82
- }
83
-
84
- return fetch(url, {
85
- method: init?.method ?? "POST",
86
- headers,
87
- body,
88
- signal: init?.signal ?? req?.signal ?? AbortSignal.timeout(300_000),
89
- })
90
- },
91
- }
92
- }
93
- }
1
+ // ④ auth.loader(getAuth) —— 请求接管
2
+ //
3
+ // 返回 baseURL / apiKey / headers / fetch(a,b),magpie 拿它发真实请求。
4
+ // 这是唯一能改请求体的地方。本项目用它干两件事:
5
+ //
6
+ // · 国际站网关强制要 organization-code(国内站不要),不带就是
7
+ // HTTP 500 "Internal Server Error."(22 字节纯文本,不是 JSON)
8
+ // · 推理模型低于 512 的 max_tokens 会被 reasoning 吃光,content 返回空串
9
+ //
10
+ // ⚠ 不要在这里读流、不要重试换号、不要手拼 x-api-key ——
11
+ // 前两件 magpie 做了,第三件看 SDK 包自动加。见 README「最小交付面」。
12
+
13
+ import { ensureKey, accountOf } from "../keys.mjs"
14
+
15
+ export function makeLoader(c) {
16
+ return async (getAuth) => {
17
+ const auth = await getAuth()
18
+ if (!auth) return {}
19
+ return {
20
+ baseURL: c.BASE,
21
+ apiKey: auth.access,
22
+ headers: { "user-agent": "MagicrewClient/1.0" },
23
+ async fetch(input, init) {
24
+ const fresh = await getAuth()
25
+ if (!fresh) throw new Error(`${c.SHORT} 未登录`)
26
+
27
+ // key 只有一个来源(src/keys.mjs)—— 服务端签一把顶一把,多入口必互相吊销
28
+ const key = await ensureKey(c, getAuth)
29
+ if (!key) throw new Error(`${c.SHORT} 未登录(拿不到可用的 key)`)
30
+ const account = accountOf(fresh)
31
+
32
+ const req = input instanceof Request ? input : null
33
+ const url =
34
+ typeof input === "string"
35
+ ? input
36
+ : input instanceof URL
37
+ ? input.toString()
38
+ : (req?.url ?? "")
39
+
40
+ const incoming = new Headers(init?.headers ?? req?.headers ?? {})
41
+ const headers = {
42
+ authorization: `Bearer ${key.apiKey}`,
43
+ "content-type": "application/json",
44
+ }
45
+ if (c.needsOrgOnGateway && fresh.org) headers["organization-code"] = fresh.org
46
+
47
+ for (const [k, v] of incoming) {
48
+ if (["authorization", "host", "content-length", "user-agent"].includes(k.toLowerCase())) continue
49
+ headers[k] = v
50
+ }
51
+
52
+ let body = init?.body
53
+ if (body === undefined && req) body = await req.text()
54
+
55
+ // 改写两处请求体,都为实测出来的:
56
+ //
57
+ // ① max_tokens 抬到 512
58
+ // 实测 glm-5.3-flash:64 → finish_reason:"length" 且 content:""
59
+ // 512 → 正常出话(reasoning 208 / total 233)
60
+ //
61
+ // ② 流式请求补 stream_options.include_usage
62
+ // magicrew 在 stream:true 下**默认不返回 usage**;补上之后,[DONE]
63
+ // 之前会多一条 {"choices":[],"usage":{...}} 的收尾 chunk。
64
+ // magpie 新版靠这条收尾 chunk 判定「流正常结束了」——缺了它就报
65
+ // 「Stream ended without finish_reason」然后重试(实测 2026-10-27)。
66
+ // 控制台里看到的就是:模型请求重试 (3/5) · Stream ended without finish_reason
67
+ if (typeof body === "string" && body.includes('"stream"')) {
68
+ try {
69
+ const j = JSON.parse(body)
70
+ if (Number(j.max_tokens) > 0 && Number(j.max_tokens) < 512) j.max_tokens = 512
71
+ if (j.stream === true && !j.stream_options) {
72
+ j.stream_options = { include_usage: true }
73
+ }
74
+ body = JSON.stringify(j)
75
+ } catch {}
76
+ } else if (typeof body === "string" && body.includes('"max_tokens"')) {
77
+ try {
78
+ const j = JSON.parse(body)
79
+ if (Number(j.max_tokens) > 0 && Number(j.max_tokens) < 512) j.max_tokens = 512
80
+ body = JSON.stringify(j)
81
+ } catch {}
82
+ }
83
+
84
+ return fetch(url, {
85
+ method: init?.method ?? "POST",
86
+ headers,
87
+ body,
88
+ signal: init?.signal ?? req?.signal ?? AbortSignal.timeout(300_000),
89
+ })
90
+ },
91
+ }
92
+ }
93
+ }