dsh-cmtoken-oauth 1.5.2 → 1.5.3
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 +15 -0
- package/lib/index.js +88 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,21 @@ lib/client.js web:设置页「cmtoken 认证」向导(视觉模型标 👁
|
|
|
69
69
|
lib/qr-core.cjs 内联 QR 编码器(输出 SVG)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## 变更记录
|
|
73
|
+
|
|
74
|
+
### 1.5.3(2026-09-11)
|
|
75
|
+
|
|
76
|
+
- 修复 DSH 0.1.5+(desktop 0.1.5-rc.1 起)下插件树加载失败:
|
|
77
|
+
`cannot get property "webServer" without inject`。
|
|
78
|
+
- 根因:新版 `connection.rpc.handle(channel, handler)` 在注册时执行
|
|
79
|
+
`owner.webServer.register(route)`,而 `owner` 恒为 connection 插件自身的 ctx
|
|
80
|
+
(其 inject 只有 `credentials`),第三方插件调用必然抛错。
|
|
81
|
+
- 改法:在 `ctx.inject(['webServer'])` 子上下文用
|
|
82
|
+
`ctx.webServer.register({ kind: 'prefix', path: '/cmtoken-oauth', handler })`
|
|
83
|
+
自注册 RPC 前缀路由,并沿用 connection 的
|
|
84
|
+
`client-request` / `server-response` 信封协议,客户端代码无需改动;
|
|
85
|
+
另加 loopback 来源校验。
|
|
86
|
+
|
|
72
87
|
## License
|
|
73
88
|
|
|
74
89
|
MIT
|
package/lib/index.js
CHANGED
|
@@ -218,8 +218,81 @@ function errorResult(e) {
|
|
|
218
218
|
return { ok: false, error: { code: 'bad-request', message, details: {} } };
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
/**
|
|
222
|
+
* DSH 0.1.5+ 兼容的 RPC 传输层。
|
|
223
|
+
*
|
|
224
|
+
* 旧版通过 `ctx.connection.rpc.handle(channel, handler)` 注册通道;新版
|
|
225
|
+
* connection 服务把路由挂到「调用方 fiber」的 webServer 上
|
|
226
|
+
* (dsh-client-connection/lib/index.js `owner.webServer.register(route)`),
|
|
227
|
+
* 而 owner 恒为 connection 插件自身的 ctx(其 inject 只有 credentials),
|
|
228
|
+
* 因此第三方插件调用必然抛 `cannot get property "webServer" without inject`。
|
|
229
|
+
*
|
|
230
|
+
* 改为在声明了 webServer 的子上下文里自注册一条 prefix 路由,并沿用
|
|
231
|
+
* connection 的 RPC 信封协议(client-request / server-response),
|
|
232
|
+
* 客户端代码无需改动。
|
|
233
|
+
*/
|
|
234
|
+
function registerHttpRpc(ctx, channel, dispatch) {
|
|
235
|
+
const webServer = ctx.get?.('webServer') ?? ctx.webServer;
|
|
236
|
+
if (webServer === undefined || typeof webServer.register !== 'function') {
|
|
237
|
+
ctx.logger?.warn?.(`[cmtoken-oauth] webServer 服务不可用,${channel} 通道未注册`);
|
|
238
|
+
return () => {};
|
|
239
|
+
}
|
|
240
|
+
const isLoopback = (req) => {
|
|
241
|
+
const addr = req.socket?.remoteAddress ?? '';
|
|
242
|
+
return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';
|
|
243
|
+
};
|
|
244
|
+
return webServer.register({
|
|
245
|
+
name: 'cmtoken-oauth-rpc',
|
|
246
|
+
kind: 'prefix',
|
|
247
|
+
path: channel,
|
|
248
|
+
handler: async (req, res) => {
|
|
249
|
+
const reply = (status, payload) => {
|
|
250
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
251
|
+
res.end(payload === undefined ? '' : JSON.stringify(payload));
|
|
252
|
+
};
|
|
253
|
+
if (req.method !== 'POST' || !isLoopback(req)) return reply(404);
|
|
254
|
+
let endpoint;
|
|
255
|
+
try {
|
|
256
|
+
const pathname = new URL(req.url ?? '/', 'http://dsh.internal').pathname;
|
|
257
|
+
if (!pathname.startsWith(channel + '/')) return reply(404);
|
|
258
|
+
endpoint = decodeURIComponent(pathname.slice(channel.length + 1));
|
|
259
|
+
} catch {
|
|
260
|
+
return reply(400);
|
|
261
|
+
}
|
|
262
|
+
if (endpoint === '' || endpoint.includes('/')) return reply(404);
|
|
263
|
+
|
|
264
|
+
const abort = new AbortController();
|
|
265
|
+
res.on('close', () => { if (!res.writableEnded) abort.abort(); });
|
|
266
|
+
const chunks = [];
|
|
267
|
+
try {
|
|
268
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
269
|
+
} catch {
|
|
270
|
+
return reply(400);
|
|
271
|
+
}
|
|
272
|
+
let envelope;
|
|
273
|
+
try {
|
|
274
|
+
envelope = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
275
|
+
} catch {
|
|
276
|
+
return reply(400);
|
|
277
|
+
}
|
|
278
|
+
const rpcId = typeof envelope?.rpcId === 'string' ? envelope.rpcId : 'unknown';
|
|
279
|
+
const method = typeof envelope?.method === 'string' ? envelope.method : endpoint;
|
|
280
|
+
let result;
|
|
281
|
+
try {
|
|
282
|
+
result = await dispatch(method, envelope?.payload, abort.signal);
|
|
283
|
+
} catch (e) {
|
|
284
|
+
result = errorResult(e);
|
|
285
|
+
}
|
|
286
|
+
if (result === null || typeof result !== 'object' || typeof result.ok !== 'boolean') {
|
|
287
|
+
result = { ok: true, value: result ?? {} };
|
|
288
|
+
}
|
|
289
|
+
reply(200, { type: 'server-response', rpcId, result });
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
221
294
|
function registerRpc(ctx) {
|
|
222
|
-
return ctx
|
|
295
|
+
return registerHttpRpc(ctx, '/cmtoken-oauth', async (endpoint, rawPayload, signal) => {
|
|
223
296
|
try {
|
|
224
297
|
if (signal?.aborted) throw new Error('请求已取消。');
|
|
225
298
|
const payload = (rawPayload && typeof rawPayload === 'object') ? rawPayload : {};
|
|
@@ -347,7 +420,7 @@ function registerRpc(ctx) {
|
|
|
347
420
|
} catch (e) {
|
|
348
421
|
return errorResult(e);
|
|
349
422
|
}
|
|
350
|
-
}
|
|
423
|
+
});
|
|
351
424
|
}
|
|
352
425
|
|
|
353
426
|
async function writeConfigAndCreds(ctx, token, models) {
|
|
@@ -609,7 +682,7 @@ function registerVisionTool(ctx) {
|
|
|
609
682
|
const tool = {
|
|
610
683
|
name: VISION_TOOL_NAME,
|
|
611
684
|
description:
|
|
612
|
-
'通过 cmtoken
|
|
685
|
+
'通过 cmtoken 视觉模型读取图片。当对话中出现当前模型无法查看的图片时使用,典型形态是 [Image: <本地路径>] 行(粘贴/拖入的图片由插件自动落盘)或裸的本地绝对路径、http(s) URL:截图、照片、图表、扫描件等。返回结构化取证结果(summary、ocr_full_text、layout、semantics、uncertainty),请引用证据而不是猜测。需要已完成 cmtoken 一键认证。',
|
|
613
686
|
parameters: {
|
|
614
687
|
type: 'object',
|
|
615
688
|
properties: {
|
|
@@ -825,16 +898,13 @@ function modelLabelToName(label) {
|
|
|
825
898
|
return s.replace(/\s*\(视觉回退\)\s*$/i, '').trim();
|
|
826
899
|
}
|
|
827
900
|
|
|
828
|
-
/** 在已认证模型列表里查找(id
|
|
829
|
-
* 绝不做子串包含——否则其他 provider 的同名系模型(如 zai-coding-cn 的
|
|
830
|
-
* glm-5.3-flash 包含 cmtoken 组 "glm-5" 的字样)会被误判为 cmtoken 模型而遭到
|
|
831
|
-
* 拖图接管,违背「本插件只服务 cmtoken 提供方」的设计边界;找不到返回 undefined。 */
|
|
901
|
+
/** 在已认证模型列表里查找(id 或显示名,忽略大小写;兜底子串匹配);找不到返回 undefined。 */
|
|
832
902
|
function findConfiguredModel(ctx, name) {
|
|
833
903
|
if (!name) return undefined;
|
|
834
904
|
const models = ctx.settings?.get?.(settingsNamespace('llm-pi-ai'))?.providers?.cmtoken?.models ?? [];
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
905
|
+
const lower = String(name).toLowerCase();
|
|
906
|
+
return models.find((m) => String(m.id).toLowerCase() === lower || String(m.name ?? '').toLowerCase() === lower)
|
|
907
|
+
?? models.find((m) => lower.includes(String(m.id).toLowerCase()) || lower.includes(String(m.name ?? '').toLowerCase()));
|
|
838
908
|
}
|
|
839
909
|
|
|
840
910
|
// ---------------- 视觉回退模型选择(~/.dsh/cmtoken-oauth.json) ----------------
|
|
@@ -1082,8 +1152,12 @@ export function apply(ctx, cfg = {}) {
|
|
|
1082
1152
|
let removeRpc = () => {};
|
|
1083
1153
|
let stopTimer = () => {};
|
|
1084
1154
|
let disposeVisionAdapter = () => {};
|
|
1155
|
+
// 在声明了 webServer 的子上下文里注册 RPC 前缀路由(DSH 0.1.5+)。
|
|
1156
|
+
const rpcScope = ctx.inject(['webServer'], (scope) => {
|
|
1157
|
+
removeRpc = registerRpc(scope);
|
|
1158
|
+
});
|
|
1159
|
+
void rpcScope;
|
|
1085
1160
|
ctx.effect(() => {
|
|
1086
|
-
removeRpc = registerRpc(ctx);
|
|
1087
1161
|
stopTimer = startAutoRefresh(ctx);
|
|
1088
1162
|
registerVisionTool(ctx);
|
|
1089
1163
|
registerPasteRoute(ctx);
|
|
@@ -1102,3 +1176,6 @@ export function apply(ctx, cfg = {}) {
|
|
|
1102
1176
|
}, 'cmtoken-oauth: host service');
|
|
1103
1177
|
ctx.logger?.info?.('[cmtoken-oauth] host 服务已启动(自动刷新已开启:启动即补刷 + 每 1 分钟检查 + status 查询按需触发,剩余 <1 小时或已过期即刷新)');
|
|
1104
1178
|
}
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cmtoken-oauth",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "DeepSeek Harness 插件:在 DSH 设置页一键完成广东移动 MaaS(cmtoken)的 OAuth 2.0 Device Grant + PKCE 认证,自动发现模型并写入 llm-pi-ai provider 配置(视觉模型自动声明图片输入模态);粘贴识图让纯文本模型直接收图(Qwen3.8 / Qwen3.x / Kimi K2.x / MiniMax M3 自动识读),另有 cmtoken_read_image 工具与可选的 cmtoken-vision 包装路由;凭据安全保存并自动续期。零第三方运行时依赖。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|