@jingyi0605/codingns4dsh 0.1.7-rc.3 → 0.2.0-beta.1
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/LICENSE +695 -0
- package/README.en.md +23 -9
- package/README.md +23 -9
- package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
- package/data/build/dist/bootstrap/index.d.ts +1 -1
- package/data/build/dist/client/bundle.js +1200 -170
- package/data/build/dist/client/context-breakdown-dom.d.ts +53 -0
- package/data/build/dist/client/context-breakdown-dom.js +171 -0
- package/data/build/dist/client/dsh-h5-bootstrap.js +7 -1
- package/data/build/dist/client/features/cli-adapters.js +11 -0
- package/data/build/dist/client/features/index.d.ts +1 -0
- package/data/build/dist/client/features/index.js +1 -0
- package/data/build/dist/client/features/subscription-usage-panel.d.ts +15 -0
- package/data/build/dist/client/features/subscription-usage-panel.js +117 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +22 -5
- package/data/build/dist/client/features/workspace-session-enhancement.js +17 -2
- package/data/build/dist/client/git-management.js +80 -10
- package/data/build/dist/client/index.d.ts +1 -0
- package/data/build/dist/client/index.js +16 -49
- package/data/build/dist/client/locale.js +20 -2
- package/data/build/dist/client/remote-web-context.js +26 -2
- package/data/build/dist/client/settings-bridge.d.ts +20 -5
- package/data/build/dist/client/settings-bridge.js +30 -7
- package/data/build/dist/client/subscription-slot.d.ts +3 -1
- package/data/build/dist/client/subscription-slot.js +31 -6
- package/data/build/dist/client/terminal/ui.js +1 -1
- package/data/build/dist/client/terminal/xterm-view.js +1 -1
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +10 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +22 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +5 -2
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +3 -2
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +2 -2
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +22 -1
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +29 -3
- package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +2 -2
- package/data/build/dist/dsh-capabilities/matrix.js +31 -1
- package/data/build/dist/dsh-capabilities/routes.d.ts +8 -1
- package/data/build/dist/dsh-capabilities/routes.js +173 -5
- package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
- package/data/build/dist/features/registry.d.ts +7 -0
- package/data/build/dist/features/registry.js +20 -2
- package/data/build/dist/host/cli-adapters/feature.js +24 -2
- package/data/build/dist/host/cli-adapters/native-team-proxy.d.ts +36 -0
- package/data/build/dist/host/cli-adapters/native-team-proxy.js +108 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +35 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.js +405 -5
- package/data/build/dist/host/cli-adapters/registry.d.ts +9 -3
- package/data/build/dist/host/cli-adapters/registry.js +30 -0
- package/data/build/dist/host/cli-adapters/session-store.d.ts +8 -2
- package/data/build/dist/host/cli-adapters/session-store.js +19 -0
- package/data/build/dist/host/features/git-management.js +41 -18
- package/data/build/dist/host/features/types.d.ts +6 -3
- package/data/build/dist/host/index-injection.d.ts +12 -5
- package/data/build/dist/host/index-injection.js +32 -11
- package/data/build/dist/host/index.js +15 -4
- package/data/build/dist/host/lan-access-dsh.d.ts +2 -2
- package/data/build/dist/host/native-session-bridge.d.ts +4 -0
- package/data/build/dist/host/native-session-bridge.js +51 -9
- package/data/build/dist/host/relay-tunnel-runtime.js +10 -1
- package/data/build/dist/host/remote-web-runtime.js +40 -17
- package/data/build/dist/host/rpc.d.ts +24 -5
- package/data/build/dist/host/rpc.js +38 -9
- package/data/build/dist/host/session-migration-repair.js +14 -0
- package/data/build/dist/host/settings.d.ts +2 -2
- package/data/build/dist/host/settings.js +11 -1
- package/data/build/dist/host/terminal/startup.d.ts +2 -2
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +22 -1
- package/data/build/dist/shared/contracts/config.d.ts +21 -0
- package/data/build/dist/shared/contracts/config.js +17 -0
- package/data/build/dist/shared/contracts/errors.d.ts +2 -2
- package/data/build/dist/shared/contracts/subscription.d.ts +7 -0
- package/data/build/dist/shared/contracts/subscription.js +14 -1
- package/data/build/dist/shared/contracts/version.d.ts +4 -4
- package/data/build/dist/shared/contracts/version.js +3 -3
- package/data/build/dist/shared/index.d.ts +3 -2
- package/data/build/dist/shared/index.js +2 -1
- package/data/build/dist/transport/carrier.d.ts +11 -1
- package/data/build/dist/transport/carrier.js +77 -26
- package/data/build/dist/transport/debug.d.ts +5 -0
- package/data/build/dist/transport/debug.js +17 -2
- package/data/build/dist/transport/dsh-session.d.ts +18 -1
- package/data/build/dist/transport/dsh-session.js +48 -13
- package/data/build/dist/transport/index.d.ts +1 -1
- package/data/build/dist/transport/index.js +1 -1
- package/package.json +23 -19
- package/profile/README.md +2 -2
- package/profile/package.json +4 -3
- package/profile/version.json +3 -3
- package/version.json +3 -3
|
@@ -15,14 +15,20 @@ export class ProviderSubscriptionService {
|
|
|
15
15
|
sub2api;
|
|
16
16
|
deepseek;
|
|
17
17
|
official;
|
|
18
|
+
kimi;
|
|
19
|
+
grok;
|
|
18
20
|
constructor(options = {}) {
|
|
21
|
+
// 全局超时只作为缺省值;单项服务显式给出的 timeoutMs 优先。
|
|
22
|
+
const shared = options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs };
|
|
19
23
|
this.commandCode = options.commandCode;
|
|
20
|
-
this.codex = new CodexSubscriptionService(options.codex);
|
|
21
|
-
this.claudeCode = new ClaudeCodeSubscriptionService(options.claudeCode);
|
|
24
|
+
this.codex = new CodexSubscriptionService({ ...shared, ...options.codex });
|
|
25
|
+
this.claudeCode = new ClaudeCodeSubscriptionService({ ...shared, ...options.claudeCode });
|
|
22
26
|
this.opencode = new OpenCodeSubscriptionService(options.opencode);
|
|
23
|
-
this.sub2api = new Sub2ApiUsageService(options.sub2api);
|
|
24
|
-
this.deepseek = new DeepseekSubscriptionService(options.deepseek);
|
|
25
|
-
this.official = new OfficialProviderSubscriptionService(options.official);
|
|
27
|
+
this.sub2api = new Sub2ApiUsageService({ ...shared, ...options.sub2api });
|
|
28
|
+
this.deepseek = new DeepseekSubscriptionService({ ...shared, ...options.deepseek });
|
|
29
|
+
this.official = new OfficialProviderSubscriptionService({ ...shared, ...options.official });
|
|
30
|
+
this.kimi = new KimiSubscriptionService({ ...shared, ...options.kimi });
|
|
31
|
+
this.grok = new GrokSubscriptionService({ ...shared, ...options.grok });
|
|
26
32
|
}
|
|
27
33
|
read(adapterId, providerId) {
|
|
28
34
|
if (adapterId === 'dsh')
|
|
@@ -35,6 +41,7 @@ export class ProviderSubscriptionService {
|
|
|
35
41
|
case 'codex': return this.codex.read();
|
|
36
42
|
case 'claude-code': return this.claudeCode.read();
|
|
37
43
|
case 'opencode': return this.opencode.read();
|
|
44
|
+
case 'kimi': return this.kimi.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'kimi-coding' }), ''));
|
|
38
45
|
default: return Promise.resolve(null);
|
|
39
46
|
}
|
|
40
47
|
}
|
|
@@ -71,6 +78,8 @@ export class ProviderSubscriptionService {
|
|
|
71
78
|
return this.codex.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'openai-codex' }), ''));
|
|
72
79
|
if (adapterId === 'claude-code')
|
|
73
80
|
return this.claudeCode.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'anthropic' }), ''));
|
|
81
|
+
if (adapterId === 'grok')
|
|
82
|
+
return this.grok.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'xai' }), ''));
|
|
74
83
|
return null;
|
|
75
84
|
}
|
|
76
85
|
}
|
|
@@ -304,6 +313,338 @@ export class OpenCodeSubscriptionService {
|
|
|
304
313
|
return null;
|
|
305
314
|
}
|
|
306
315
|
}
|
|
316
|
+
/** 读取 Kimi Code 官方套餐额度;本地 OAuth 凭据只在 Host 内使用,不做刷新只读访问令牌。 */
|
|
317
|
+
export class KimiSubscriptionService {
|
|
318
|
+
request;
|
|
319
|
+
timeoutMs;
|
|
320
|
+
credentialPaths;
|
|
321
|
+
constructor(options = {}) {
|
|
322
|
+
this.request = options.fetch ?? fetch;
|
|
323
|
+
this.timeoutMs = options.timeoutMs ?? 8_000;
|
|
324
|
+
this.credentialPaths = options.credentials ?? defaultKimiCredentialPaths();
|
|
325
|
+
}
|
|
326
|
+
async read() {
|
|
327
|
+
const token = this.readAccessToken();
|
|
328
|
+
if (token === null)
|
|
329
|
+
return null;
|
|
330
|
+
for (const baseUrl of kimiCodeBaseUrls(token.region)) {
|
|
331
|
+
const usage = await this.readUsage(baseUrl, token.value);
|
|
332
|
+
if (usage !== null)
|
|
333
|
+
return usage;
|
|
334
|
+
}
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
readAccessToken() {
|
|
338
|
+
for (const path of this.credentialPaths) {
|
|
339
|
+
const credentials = readJson(path);
|
|
340
|
+
const token = textValue(credentials?.access_token);
|
|
341
|
+
if (token === null)
|
|
342
|
+
continue;
|
|
343
|
+
const expiresAt = numberValue(credentials?.expires_at);
|
|
344
|
+
// 访问令牌有效期很短;已过期或临期的令牌直接跳过,由 CLI 自己负责刷新。
|
|
345
|
+
if (expiresAt !== null && expiresAt * 1000 <= Date.now() + 30_000)
|
|
346
|
+
continue;
|
|
347
|
+
return { value: token, region: kimiTokenRegion(token) };
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
async readUsage(baseUrl, token) {
|
|
352
|
+
const controller = new AbortController();
|
|
353
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
354
|
+
try {
|
|
355
|
+
const response = await this.request(`${baseUrl}/usages`, {
|
|
356
|
+
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
357
|
+
signal: controller.signal,
|
|
358
|
+
});
|
|
359
|
+
if (!response.ok)
|
|
360
|
+
return null;
|
|
361
|
+
return normalizeKimiUsage(await response.json());
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
clearTimeout(timer);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const GROK_BILLING_ENDPOINT = 'https://grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig';
|
|
372
|
+
const GROK_OIDC_SCOPE_PREFIX = 'https://auth.x.ai::';
|
|
373
|
+
const GROK_LEGACY_SESSION_SCOPE = 'https://accounts.x.ai/sign-in';
|
|
374
|
+
/** 读取 Grok(xAI)官方 SuperGrok 订阅额度;只读 Grok CLI 的 OAuth 凭据(~/.grok/auth.json),不做刷新,过期令牌也照常尝试以容忍时钟偏差。 */
|
|
375
|
+
export class GrokSubscriptionService {
|
|
376
|
+
request;
|
|
377
|
+
timeoutMs;
|
|
378
|
+
credentialPaths;
|
|
379
|
+
constructor(options = {}) {
|
|
380
|
+
this.request = options.fetch ?? fetch;
|
|
381
|
+
this.timeoutMs = options.timeoutMs ?? 15_000;
|
|
382
|
+
this.credentialPaths = options.credentials ?? defaultGrokCredentialPaths();
|
|
383
|
+
}
|
|
384
|
+
async read() {
|
|
385
|
+
const token = this.readAccessToken();
|
|
386
|
+
if (token === null)
|
|
387
|
+
return null;
|
|
388
|
+
const controller = new AbortController();
|
|
389
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
390
|
+
try {
|
|
391
|
+
// 空 gRPC-web 帧(1 字节 flags + 4 字节大端长度 0)即可取回账单快照。
|
|
392
|
+
const response = await this.request(GROK_BILLING_ENDPOINT, {
|
|
393
|
+
method: 'POST',
|
|
394
|
+
headers: {
|
|
395
|
+
Authorization: `Bearer ${token}`,
|
|
396
|
+
Origin: 'https://grok.com',
|
|
397
|
+
Referer: 'https://grok.com/?_s=usage',
|
|
398
|
+
Accept: '*/*',
|
|
399
|
+
'Content-Type': 'application/grpc-web+proto',
|
|
400
|
+
'x-grpc-web': '1',
|
|
401
|
+
'x-user-agent': 'connect-es/2.1.1',
|
|
402
|
+
},
|
|
403
|
+
body: new Uint8Array(5),
|
|
404
|
+
signal: controller.signal,
|
|
405
|
+
});
|
|
406
|
+
// 鉴权失败与其他 HTTP 错误都不展示陈旧数据;令牌过期时由 Grok CLI 重新登录后恢复。
|
|
407
|
+
if (!response.ok)
|
|
408
|
+
return null;
|
|
409
|
+
const headerStatus = numberValue(response.headers.get('grpc-status') ?? undefined);
|
|
410
|
+
if (headerStatus !== null && headerStatus !== 0)
|
|
411
|
+
return null;
|
|
412
|
+
const raw = new Uint8Array(await response.arrayBuffer());
|
|
413
|
+
const trailerStatus = numberValue(grpcWebTrailerFields(raw)['grpc-status']);
|
|
414
|
+
if (trailerStatus !== null && trailerStatus !== 0)
|
|
415
|
+
return null;
|
|
416
|
+
const nowSecs = Math.floor(Date.now() / 1000);
|
|
417
|
+
const snapshot = parseGrokBillingPayload(raw, nowSecs);
|
|
418
|
+
if (snapshot === null)
|
|
419
|
+
return null;
|
|
420
|
+
const usedPercent = clamp(snapshot.usedPercent);
|
|
421
|
+
return {
|
|
422
|
+
authenticated: true,
|
|
423
|
+
planType: null,
|
|
424
|
+
primary: { usedPercent, remainingPercent: 100 - usedPercent, windowDurationMins: grokWindowDurationMins(snapshot.resetsAt, nowSecs), resetsAt: snapshot.resetsAt },
|
|
425
|
+
secondary: null,
|
|
426
|
+
monthly: null,
|
|
427
|
+
rateLimitReachedType: null,
|
|
428
|
+
resetCredits: null,
|
|
429
|
+
capturedAt: new Date().toISOString(),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
clearTimeout(timer);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
readAccessToken() {
|
|
440
|
+
for (const path of this.credentialPaths) {
|
|
441
|
+
const auth = readJson(path);
|
|
442
|
+
if (auth === null)
|
|
443
|
+
continue;
|
|
444
|
+
const token = selectGrokAuthToken(auth);
|
|
445
|
+
if (token !== null)
|
|
446
|
+
return token;
|
|
447
|
+
}
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function defaultGrokCredentialPaths() {
|
|
452
|
+
return [join(process.env.GROK_HOME ?? join(homedir(), '.grok'), 'auth.json')];
|
|
453
|
+
}
|
|
454
|
+
/** auth.json 以 OIDC scope URL 为键;优先 SuperGrok 的 auth.x.ai 条目,回退 legacy 登录条目,残缺条目不遮蔽健康条目。 */
|
|
455
|
+
function selectGrokAuthToken(auth) {
|
|
456
|
+
let oidc = null;
|
|
457
|
+
let legacy = null;
|
|
458
|
+
for (const [scope, value] of Object.entries(auth)) {
|
|
459
|
+
const key = textValue(recordValue(value)?.key);
|
|
460
|
+
if (key === null)
|
|
461
|
+
continue;
|
|
462
|
+
if (scope.startsWith(GROK_OIDC_SCOPE_PREFIX))
|
|
463
|
+
oidc = key;
|
|
464
|
+
else if (scope === GROK_LEGACY_SESSION_SCOPE || scope.includes('/sign-in'))
|
|
465
|
+
legacy = key;
|
|
466
|
+
}
|
|
467
|
+
return oidc ?? legacy;
|
|
468
|
+
}
|
|
469
|
+
/** 按重置时间距今的天数推断窗口长度(4–12 天按周窗口、20–45 天按月窗口),无法推断时留空由客户端使用兜底标签。 */
|
|
470
|
+
function grokWindowDurationMins(resetsAt, nowSecs) {
|
|
471
|
+
if (resetsAt === null)
|
|
472
|
+
return null;
|
|
473
|
+
const days = Math.round((resetsAt - nowSecs) / 86_400);
|
|
474
|
+
if (days >= 4 && days <= 12)
|
|
475
|
+
return 7 * 24 * 60;
|
|
476
|
+
if (days >= 20 && days <= 45)
|
|
477
|
+
return 30 * 24 * 60;
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* 账单端点没有公开 .proto,按字段路径启发式提取(移植自 cc-switch / CodexBar):
|
|
482
|
+
* 百分比取路径末段为 1、值域 [0,100] 的最浅 fixed32 字段;重置时间取落在合理 Unix 秒区间且
|
|
483
|
+
* 晚于当前时刻的 varint(优先路径 [1,5,1]);proto3 会省略 0 值字段,存在重置时间与周期标记时按 0% 处理。
|
|
484
|
+
*/
|
|
485
|
+
function parseGrokBillingPayload(data, nowSecs) {
|
|
486
|
+
let payloads = grpcWebDataFrames(data);
|
|
487
|
+
if (payloads.length === 0 && looksLikeProtobufPayload(data))
|
|
488
|
+
payloads = [data];
|
|
489
|
+
if (payloads.length === 0)
|
|
490
|
+
return null;
|
|
491
|
+
const scan = { fixed32: [], varint: [] };
|
|
492
|
+
for (const payload of payloads)
|
|
493
|
+
scanGrokProtobuf(payload, 0, [], 0, scan);
|
|
494
|
+
const percent = scan.fixed32
|
|
495
|
+
.filter((field) => field.path.at(-1) === 1 && Number.isFinite(field.value) && field.value >= 0 && field.value <= 100)
|
|
496
|
+
.sort((left, right) => (left.path.length - right.path.length) || (left.order - right.order))[0]?.value;
|
|
497
|
+
const resetCandidates = scan.varint.filter((field) => field.value >= 1_700_000_000 && field.value <= 2_100_000_000 && field.value > nowSecs);
|
|
498
|
+
const preferred = resetCandidates.filter((field) => field.path.length === 3 && field.path[0] === 1 && field.path[1] === 5 && field.path[2] === 1);
|
|
499
|
+
const resetPool = preferred.length > 0 ? preferred : resetCandidates;
|
|
500
|
+
const reset = resetPool.length > 0 ? Math.min(...resetPool.map((field) => field.value)) : null;
|
|
501
|
+
const hasUsagePeriod = scan.varint.some((field) => (field.path.length >= 2 && field.path[0] === 1 && field.path[1] === 6)
|
|
502
|
+
|| (field.path.length === 3 && field.path[0] === 1 && field.path[1] === 8 && field.path[2] === 1 && (field.value === 1 || field.value === 2)));
|
|
503
|
+
const usedPercent = percent ?? (scan.fixed32.length === 0 && reset !== null && hasUsagePeriod ? 0 : undefined);
|
|
504
|
+
if (usedPercent === undefined)
|
|
505
|
+
return null;
|
|
506
|
+
return { usedPercent, resetsAt: reset };
|
|
507
|
+
}
|
|
508
|
+
/** 拆出 gRPC-web data 帧(flags 高位 0x80 的 trailer 帧跳过);任一帧长度非法时返回空,调用方再按裸 protobuf 兜底。 */
|
|
509
|
+
function grpcWebDataFrames(data) {
|
|
510
|
+
const frames = [];
|
|
511
|
+
let index = 0;
|
|
512
|
+
while (index < data.length) {
|
|
513
|
+
if (index + 5 > data.length)
|
|
514
|
+
return [];
|
|
515
|
+
const flags = data[index];
|
|
516
|
+
const length = data[index + 1] * 16_777_216 + data[index + 2] * 65_536 + data[index + 3] * 256 + data[index + 4];
|
|
517
|
+
const start = index + 5;
|
|
518
|
+
const end = start + length;
|
|
519
|
+
if (end > data.length)
|
|
520
|
+
return [];
|
|
521
|
+
if ((flags & 0x80) === 0)
|
|
522
|
+
frames.push(data.subarray(start, end));
|
|
523
|
+
index = end;
|
|
524
|
+
}
|
|
525
|
+
return frames;
|
|
526
|
+
}
|
|
527
|
+
/** 响应体没有帧头时,看首字节是否像合法 protobuf tag(某些成功请求直接返回裸 protobuf)。 */
|
|
528
|
+
function looksLikeProtobufPayload(data) {
|
|
529
|
+
const first = data[0];
|
|
530
|
+
if (first === undefined)
|
|
531
|
+
return false;
|
|
532
|
+
const wireType = first & 0x07;
|
|
533
|
+
return first >> 3 > 0 && (wireType === 0 || wireType === 1 || wireType === 2 || wireType === 5);
|
|
534
|
+
}
|
|
535
|
+
/** 从 trailer 帧(flags & 0x80)解析 grpc-status / grpc-message 等字段。 */
|
|
536
|
+
function grpcWebTrailerFields(data) {
|
|
537
|
+
const fields = {};
|
|
538
|
+
let index = 0;
|
|
539
|
+
while (index + 5 <= data.length) {
|
|
540
|
+
const flags = data[index];
|
|
541
|
+
const length = data[index + 1] * 16_777_216 + data[index + 2] * 65_536 + data[index + 3] * 256 + data[index + 4];
|
|
542
|
+
const start = index + 5;
|
|
543
|
+
const end = start + length;
|
|
544
|
+
if (end > data.length)
|
|
545
|
+
break;
|
|
546
|
+
if ((flags & 0x80) !== 0) {
|
|
547
|
+
for (const line of new TextDecoder().decode(data.subarray(start, end)).split(/\r?\n/u)) {
|
|
548
|
+
const separator = line.indexOf(':');
|
|
549
|
+
if (separator <= 0)
|
|
550
|
+
continue;
|
|
551
|
+
fields[line.slice(0, separator).trim().toLowerCase()] = grokPercentDecode(line.slice(separator + 1).trim());
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
index = end;
|
|
555
|
+
}
|
|
556
|
+
return fields;
|
|
557
|
+
}
|
|
558
|
+
/** gRPC message 使用 percent-encoding;解码失败的序列原样保留。 */
|
|
559
|
+
function grokPercentDecode(input) {
|
|
560
|
+
const bytes = new TextEncoder().encode(input);
|
|
561
|
+
const out = [];
|
|
562
|
+
let index = 0;
|
|
563
|
+
while (index < bytes.length) {
|
|
564
|
+
if (bytes[index] === 0x25 && index + 2 < bytes.length) {
|
|
565
|
+
const hex = String.fromCharCode(bytes[index + 1], bytes[index + 2]);
|
|
566
|
+
if (/^[0-9a-fA-F]{2}$/u.test(hex)) {
|
|
567
|
+
out.push(parseInt(hex, 16));
|
|
568
|
+
index += 3;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
out.push(bytes[index]);
|
|
573
|
+
index += 1;
|
|
574
|
+
}
|
|
575
|
+
return new TextDecoder().decode(new Uint8Array(out));
|
|
576
|
+
}
|
|
577
|
+
function readGrokVarint(bytes, state) {
|
|
578
|
+
let value = 0;
|
|
579
|
+
let shift = 0;
|
|
580
|
+
while (state.index < bytes.length && shift < 64) {
|
|
581
|
+
const byte = bytes[state.index];
|
|
582
|
+
state.index += 1;
|
|
583
|
+
value += (byte & 0x7f) * 2 ** shift;
|
|
584
|
+
if ((byte & 0x80) === 0)
|
|
585
|
+
return value;
|
|
586
|
+
shift += 7;
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
/** 递归扫描 protobuf 消息,收集 varint 与 fixed32 字段;length-delimited 一律按嵌套消息试扫(深度 ≤4),无法解析时从字段起点 +1 重新同步。 */
|
|
591
|
+
function scanGrokProtobuf(bytes, depth, path, order, scan) {
|
|
592
|
+
let index = 0;
|
|
593
|
+
let nextOrder = order;
|
|
594
|
+
while (index < bytes.length) {
|
|
595
|
+
const fieldStart = index;
|
|
596
|
+
const keyState = { index };
|
|
597
|
+
const key = readGrokVarint(bytes, keyState);
|
|
598
|
+
index = keyState.index;
|
|
599
|
+
if (key === null || key === 0) {
|
|
600
|
+
index = fieldStart + 1;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const fieldPath = [...path, Math.floor(key / 8)];
|
|
604
|
+
const wireType = key & 0x07;
|
|
605
|
+
if (wireType === 0) {
|
|
606
|
+
const valueState = { index };
|
|
607
|
+
const value = readGrokVarint(bytes, valueState);
|
|
608
|
+
if (value === null) {
|
|
609
|
+
index = fieldStart + 1;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
index = valueState.index;
|
|
613
|
+
scan.varint.push({ path: fieldPath, value });
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (wireType === 1) {
|
|
617
|
+
if (index + 8 > bytes.length)
|
|
618
|
+
return nextOrder;
|
|
619
|
+
index += 8;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
if (wireType === 2) {
|
|
623
|
+
const lengthState = { index };
|
|
624
|
+
const length = readGrokVarint(bytes, lengthState);
|
|
625
|
+
index = lengthState.index;
|
|
626
|
+
if (length === null || length > bytes.length - index) {
|
|
627
|
+
index = fieldStart + 1;
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const end = index + length;
|
|
631
|
+
if (depth < 4)
|
|
632
|
+
nextOrder = scanGrokProtobuf(bytes.subarray(index, end), depth + 1, fieldPath, nextOrder, scan);
|
|
633
|
+
index = end;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (wireType === 5) {
|
|
637
|
+
if (index + 4 > bytes.length)
|
|
638
|
+
return nextOrder;
|
|
639
|
+
scan.fixed32.push({ path: fieldPath, value: new DataView(bytes.buffer, bytes.byteOffset + index, 4).getFloat32(0, true), order: nextOrder });
|
|
640
|
+
nextOrder += 1;
|
|
641
|
+
index += 4;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
index = fieldStart + 1;
|
|
645
|
+
}
|
|
646
|
+
return nextOrder;
|
|
647
|
+
}
|
|
307
648
|
function normalizeCodexSnapshot(value) {
|
|
308
649
|
const root = recordValue(value);
|
|
309
650
|
const source = recordValue(root?.rateLimits ?? root?.rate_limits) ?? root;
|
|
@@ -376,6 +717,65 @@ function normalizeOpenCodeSnapshot(value) {
|
|
|
376
717
|
function hasSubscriptionWindow(value) {
|
|
377
718
|
return value !== null && (value.primary !== null || value.secondary !== null || value.monthly !== null);
|
|
378
719
|
}
|
|
720
|
+
function defaultKimiCredentialPaths() {
|
|
721
|
+
return [
|
|
722
|
+
join(process.env.KIMI_CODE_HOME ?? join(homedir(), '.kimi-code'), 'credentials', 'kimi-code.json'),
|
|
723
|
+
join(process.env.KIMI_HOME ?? join(homedir(), '.kimi'), 'credentials', 'kimi-code.json'),
|
|
724
|
+
];
|
|
725
|
+
}
|
|
726
|
+
/** 只解析 JWT payload 中的 region 字段用于选择就近接入点;签名校验由服务端负责。 */
|
|
727
|
+
function kimiTokenRegion(token) {
|
|
728
|
+
const parts = token.split('.');
|
|
729
|
+
const payloadPart = parts[1];
|
|
730
|
+
if (payloadPart === undefined)
|
|
731
|
+
return null;
|
|
732
|
+
try {
|
|
733
|
+
const payload = recordValue(JSON.parse(Buffer.from(payloadPart.replace(/-/gu, '+').replace(/_/gu, '/'), 'base64').toString('utf8')));
|
|
734
|
+
return textValue(payload?.region);
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function kimiCodeBaseUrls(region) {
|
|
741
|
+
const override = textValue(process.env.KIMI_CODE_BASE_URL);
|
|
742
|
+
if (override !== null)
|
|
743
|
+
return [override.replace(/\/+$/u, '')];
|
|
744
|
+
const mainland = 'https://api.kimi.com/coding/v1';
|
|
745
|
+
const global = 'https://api.kimi.ai/coding/v1';
|
|
746
|
+
return region === 'cn' ? [mainland, global] : [global, mainland];
|
|
747
|
+
}
|
|
748
|
+
function normalizeKimiUsage(value) {
|
|
749
|
+
const usages = recordValue(recordValue(value)?.usages);
|
|
750
|
+
if (usages === null)
|
|
751
|
+
return null;
|
|
752
|
+
const primary = normalizeKimiQuotaWindow(usages.limit_5h ?? usages.limit5h, 5 * 60);
|
|
753
|
+
const secondary = normalizeKimiQuotaWindow(usages.limit_7d ?? usages.limit7d, 7 * 24 * 60);
|
|
754
|
+
const monthly = normalizeKimiQuotaWindow(usages.limit_month_code ?? usages.limitMonthCode, null)
|
|
755
|
+
?? normalizeKimiQuotaWindow(usages.limit_month_total ?? usages.limitMonthTotal, null);
|
|
756
|
+
if (primary === null && secondary === null && monthly === null)
|
|
757
|
+
return null;
|
|
758
|
+
return {
|
|
759
|
+
authenticated: true,
|
|
760
|
+
planType: null,
|
|
761
|
+
primary,
|
|
762
|
+
secondary,
|
|
763
|
+
monthly,
|
|
764
|
+
rateLimitReachedType: null,
|
|
765
|
+
resetCredits: null,
|
|
766
|
+
capturedAt: new Date().toISOString(),
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function normalizeKimiQuotaWindow(value, windowDurationMins) {
|
|
770
|
+
const source = recordValue(value);
|
|
771
|
+
if (source === null)
|
|
772
|
+
return null;
|
|
773
|
+
const usedRatio = numberValue(source.used_ratio ?? source.usedRatio);
|
|
774
|
+
if (usedRatio === null)
|
|
775
|
+
return null;
|
|
776
|
+
const usedPercent = clamp(usedRatio * 100);
|
|
777
|
+
return { usedPercent, remainingPercent: 100 - usedPercent, windowDurationMins, resetsAt: timestampValue(source.reset_time ?? source.resetAt) };
|
|
778
|
+
}
|
|
379
779
|
function hasThirdPartyCodexConfig(homeDirectory) {
|
|
380
780
|
const config = readText(join(homeDirectory, 'config.toml'));
|
|
381
781
|
const configuredBaseUrl = config === null ? null : readCodexBaseUrl(config);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { CodingNsAgentEvent, CodingNsAgentQuestionResponse, CodingNsCliAdapterDescriptor, CodingNsCliAdapterId, CodingNsCliModelCatalog, CodingNsAgentPermissionResponse, CodingNsCliSessionConfig, CodingNsCliSessionRecord, CodingNsCliTurnInput } from '../../shared/contracts/cli-adapter.js';
|
|
1
|
+
import type { CodingNsAgentEvent, CodingNsAgentQuestionResponse, CodingNsCliAdapterDescriptor, CodingNsCliAdapterId, CodingNsCliModelCatalog, CodingNsAgentPermissionResponse, CodingNsCliSessionConfig, CodingNsCliSessionRecord, CodingNsCliTurnInput, CodingNsCliTeamDiagnostic } from '../../shared/contracts/cli-adapter.js';
|
|
2
2
|
import type { CodingNsCliDriver } from './driver.js';
|
|
3
3
|
import { CodingNsCliSessionStore } from './session-store.js';
|
|
4
4
|
import type { CodingNsNativeSessionBridge } from '../native-session-bridge.js';
|
|
5
5
|
import type { CodingNsSettings, CodingNsCliAdapterPreference } from '../../shared/contracts/config.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { DshHostSettingsScope } from '../../dsh-capabilities/host/config-forms-adapter.js';
|
|
7
7
|
export declare class CodingNsCliAdapterRegistry {
|
|
8
8
|
private readonly drivers;
|
|
9
9
|
private readonly sessions;
|
|
@@ -49,7 +49,7 @@ export declare class CodingNsCliAdapterRegistry {
|
|
|
49
49
|
readonly modelCacheTtlMs?: number;
|
|
50
50
|
readonly modelRetryTtlMs?: number;
|
|
51
51
|
/** 适配器级最近模型选择的持久化设置。 */
|
|
52
|
-
readonly settings?:
|
|
52
|
+
readonly settings?: DshHostSettingsScope<CodingNsSettings>;
|
|
53
53
|
});
|
|
54
54
|
private readonly sessionStore;
|
|
55
55
|
private readonly nativeSessions;
|
|
@@ -63,6 +63,12 @@ export declare class CodingNsCliAdapterRegistry {
|
|
|
63
63
|
applyEnabledSettings(settings: Readonly<Record<string, boolean>> | undefined): void;
|
|
64
64
|
isEnabled(adapterId: CodingNsCliAdapterId): boolean;
|
|
65
65
|
enabledSnapshot(): Record<string, boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* DSH 0.2 的 Team 必须由原生 TeamService 创建 continuable child、mailbox
|
|
68
|
+
* 和 task board。当前外部 CLI 仍是独立进程,不能伪装成原生成员;统一返回
|
|
69
|
+
* 可诊断结果,避免 Client 把普通 CLI 会话误显示成 Team 成员。
|
|
70
|
+
*/
|
|
71
|
+
teamDiagnostic(): CodingNsCliTeamDiagnostic;
|
|
66
72
|
setSession(sessionId: string, config: CodingNsCliSessionConfig): CodingNsCliSessionConfig;
|
|
67
73
|
/** 设置服务变更后重新载入适配器级默认选择。 */
|
|
68
74
|
syncPreferences(value: Readonly<Record<string, CodingNsCliAdapterPreference>> | undefined): void;
|
|
@@ -125,6 +125,18 @@ export class CodingNsCliAdapterRegistry {
|
|
|
125
125
|
enabledSnapshot() {
|
|
126
126
|
return Object.fromEntries(this.enabled.entries());
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* DSH 0.2 的 Team 必须由原生 TeamService 创建 continuable child、mailbox
|
|
130
|
+
* 和 task board。当前外部 CLI 仍是独立进程,不能伪装成原生成员;统一返回
|
|
131
|
+
* 可诊断结果,避免 Client 把普通 CLI 会话误显示成 Team 成员。
|
|
132
|
+
*/
|
|
133
|
+
teamDiagnostic() {
|
|
134
|
+
return {
|
|
135
|
+
supported: false,
|
|
136
|
+
code: 'DSH_TEAM_NATIVE_UNAVAILABLE',
|
|
137
|
+
message: '当前插件仅提供外部 CLI 会话适配器,尚未接入 DSH 0.2 原生 Agent Team 生命周期。',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
128
140
|
setSession(sessionId, config) {
|
|
129
141
|
if (sessionId.trim() === '')
|
|
130
142
|
throw new CodingNsRpcError('CODINGNS_CLI_INVALID_SESSION', 'sessionId 不能为空');
|
|
@@ -165,6 +177,24 @@ export class CodingNsCliAdapterRegistry {
|
|
|
165
177
|
: sameAdapter && !providerIdentityChanged && previous?.rawStoreRef
|
|
166
178
|
? { rawStoreRef: previous.rawStoreRef }
|
|
167
179
|
: {}),
|
|
180
|
+
...(config.parentSessionId?.trim()
|
|
181
|
+
? { parentSessionId: config.parentSessionId.trim() }
|
|
182
|
+
: sameAdapter && previous?.parentSessionId ? { parentSessionId: previous.parentSessionId } : {}),
|
|
183
|
+
...(config.origin === 'user' || config.origin === 'subagent' || config.origin === 'plugin'
|
|
184
|
+
? { origin: config.origin }
|
|
185
|
+
: sameAdapter && previous?.origin ? { origin: previous.origin } : {}),
|
|
186
|
+
...(config.delegationDepth !== undefined && Number.isSafeInteger(config.delegationDepth) && config.delegationDepth >= 0
|
|
187
|
+
? { delegationDepth: config.delegationDepth }
|
|
188
|
+
: sameAdapter && previous?.delegationDepth !== undefined ? { delegationDepth: previous.delegationDepth } : {}),
|
|
189
|
+
...(config.continuationId?.trim()
|
|
190
|
+
? { continuationId: config.continuationId.trim() }
|
|
191
|
+
: sameAdapter && previous?.continuationId ? { continuationId: previous.continuationId } : {}),
|
|
192
|
+
...(config.teamId?.trim()
|
|
193
|
+
? { teamId: config.teamId.trim() }
|
|
194
|
+
: sameAdapter && previous?.teamId ? { teamId: previous.teamId } : {}),
|
|
195
|
+
...(config.teamMemberId?.trim()
|
|
196
|
+
? { teamMemberId: config.teamMemberId.trim() }
|
|
197
|
+
: sameAdapter && previous?.teamMemberId ? { teamMemberId: previous.teamMemberId } : {}),
|
|
168
198
|
};
|
|
169
199
|
this.sessions.set(sessionId, normalized);
|
|
170
200
|
this.sessionStore?.upsert(sessionId, normalized);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DshHostSettingsScope } from '../../dsh-capabilities/host/config-forms-adapter.js';
|
|
2
2
|
import type { CodingNsCliAdapterId, CodingNsCliSessionConfig, CodingNsCliProviderSessionState, CodingNsCliSessionRecord, CodingNsCliSessionStatus, CodingNsSessionAdapterBinding } from '../../shared/contracts/cli-adapter.js';
|
|
3
3
|
import type { CodingNsSettings } from '../../shared/contracts/config.js';
|
|
4
4
|
/**
|
|
@@ -11,7 +11,7 @@ export interface CodingNsCliSessionPersistence {
|
|
|
11
11
|
write(records: readonly CodingNsCliSessionRecord[]): Promise<void>;
|
|
12
12
|
}
|
|
13
13
|
export interface CodingNsCliSessionStoreOptions {
|
|
14
|
-
readonly settings?:
|
|
14
|
+
readonly settings?: DshHostSettingsScope<CodingNsSettings>;
|
|
15
15
|
readonly persistence?: CodingNsCliSessionPersistence;
|
|
16
16
|
}
|
|
17
17
|
export interface CodingNsCliSessionPatch {
|
|
@@ -21,6 +21,12 @@ export interface CodingNsCliSessionPatch {
|
|
|
21
21
|
readonly providerId?: string;
|
|
22
22
|
readonly providerSessionId?: string;
|
|
23
23
|
readonly rawStoreRef?: string;
|
|
24
|
+
readonly parentSessionId?: string;
|
|
25
|
+
readonly origin?: CodingNsCliSessionRecord['origin'];
|
|
26
|
+
readonly delegationDepth?: number;
|
|
27
|
+
readonly continuationId?: string;
|
|
28
|
+
readonly teamId?: string;
|
|
29
|
+
readonly teamMemberId?: string;
|
|
24
30
|
readonly title?: string;
|
|
25
31
|
readonly cwd?: string;
|
|
26
32
|
readonly status?: CodingNsCliSessionStatus;
|
|
@@ -94,6 +94,12 @@ export class CodingNsCliSessionStore {
|
|
|
94
94
|
...(patch.providerId?.trim() ? { providerId: patch.providerId.trim() } : base?.providerId ? { providerId: base.providerId } : {}),
|
|
95
95
|
...(providerSessionId ? { providerSessionId } : base?.providerSessionId ? { providerSessionId: base.providerSessionId } : {}),
|
|
96
96
|
...(patch.rawStoreRef?.trim() ? { rawStoreRef: patch.rawStoreRef.trim() } : providerBase?.rawStoreRef ? { rawStoreRef: providerBase.rawStoreRef } : {}),
|
|
97
|
+
...(stringValue(patch.parentSessionId) ? { parentSessionId: stringValue(patch.parentSessionId) } : base?.parentSessionId ? { parentSessionId: base.parentSessionId } : {}),
|
|
98
|
+
...(isSessionOrigin(patch.origin) ? { origin: patch.origin } : base?.origin ? { origin: base.origin } : {}),
|
|
99
|
+
...(finiteNonNegativeInteger(patch.delegationDepth) ? { delegationDepth: patch.delegationDepth } : base?.delegationDepth === undefined ? {} : { delegationDepth: base.delegationDepth }),
|
|
100
|
+
...(stringValue(patch.continuationId) ? { continuationId: stringValue(patch.continuationId) } : base?.continuationId ? { continuationId: base.continuationId } : {}),
|
|
101
|
+
...(stringValue(patch.teamId) ? { teamId: stringValue(patch.teamId) } : base?.teamId ? { teamId: base.teamId } : {}),
|
|
102
|
+
...(stringValue(patch.teamMemberId) ? { teamMemberId: stringValue(patch.teamMemberId) } : base?.teamMemberId ? { teamMemberId: base.teamMemberId } : {}),
|
|
97
103
|
...(patch.title?.trim() ? { title: patch.title.trim() } : base?.title ? { title: base.title } : {}),
|
|
98
104
|
...(patch.cwd?.trim() ? { cwd: patch.cwd.trim() } : base?.cwd ? { cwd: base.cwd } : {}),
|
|
99
105
|
...(patch.lastError?.trim() ? { lastError: patch.lastError.trim() } : {}),
|
|
@@ -160,6 +166,7 @@ export class CodingNsCliSessionStore {
|
|
|
160
166
|
return;
|
|
161
167
|
if (!isStatus(value.status) || typeof value.createdAt !== 'string' || typeof value.updatedAt !== 'string')
|
|
162
168
|
return;
|
|
169
|
+
const origin = isSessionOrigin(value.origin) ? value.origin : undefined;
|
|
163
170
|
this.records.set(value.dshSessionId, {
|
|
164
171
|
dshSessionId: value.dshSessionId,
|
|
165
172
|
adapterId: value.adapterId,
|
|
@@ -168,6 +175,12 @@ export class CodingNsCliSessionStore {
|
|
|
168
175
|
...(stringValue(value.providerId) ? { providerId: stringValue(value.providerId) } : {}),
|
|
169
176
|
...(stringValue(value.providerSessionId) ? { providerSessionId: stringValue(value.providerSessionId) } : {}),
|
|
170
177
|
...(stringValue(value.rawStoreRef) ? { rawStoreRef: stringValue(value.rawStoreRef) } : {}),
|
|
178
|
+
...(stringValue(value.parentSessionId) ? { parentSessionId: stringValue(value.parentSessionId) } : {}),
|
|
179
|
+
...(origin === undefined ? {} : { origin }),
|
|
180
|
+
...(finiteNonNegativeInteger(value.delegationDepth) ? { delegationDepth: value.delegationDepth } : {}),
|
|
181
|
+
...(stringValue(value.continuationId) ? { continuationId: stringValue(value.continuationId) } : {}),
|
|
182
|
+
...(stringValue(value.teamId) ? { teamId: stringValue(value.teamId) } : {}),
|
|
183
|
+
...(stringValue(value.teamMemberId) ? { teamMemberId: stringValue(value.teamMemberId) } : {}),
|
|
171
184
|
...(stringValue(value.title) ? { title: stringValue(value.title) } : {}),
|
|
172
185
|
...(stringValue(value.cwd) ? { cwd: stringValue(value.cwd) } : {}),
|
|
173
186
|
...(stringValue(value.lastError) ? { lastError: stringValue(value.lastError) } : {}),
|
|
@@ -212,6 +225,12 @@ function isProviderState(value) {
|
|
|
212
225
|
function stringValue(value) {
|
|
213
226
|
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
214
227
|
}
|
|
228
|
+
function isSessionOrigin(value) {
|
|
229
|
+
return value === 'user' || value === 'subagent' || value === 'plugin';
|
|
230
|
+
}
|
|
231
|
+
function finiteNonNegativeInteger(value) {
|
|
232
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
233
|
+
}
|
|
215
234
|
function isRecord(value) {
|
|
216
235
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
217
236
|
}
|