dsh-router-core 0.1.0
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 +151 -0
- package/cordis.patch.yml +9 -0
- package/lib/client-registry.js +3006 -0
- package/lib/client.js +3006 -0
- package/lib/index.js +1756 -0
- package/lib/types/credential-store.d.ts +15 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/keys.d.ts +39 -0
- package/lib/types/llm/adapter.d.ts +31 -0
- package/lib/types/router/index.d.ts +73 -0
- package/lib/types/router/types.d.ts +80 -0
- package/lib/types/shared.d.ts +138 -0
- package/lib/types/supplier-config.d.ts +28 -0
- package/lib/types/suppliers/contract.d.ts +98 -0
- package/lib/types/suppliers/loader.d.ts +41 -0
- package/lib/types/suppliers/registry.d.ts +33 -0
- package/package.json +86 -0
- package/src/client/CombosTab.tsx +458 -0
- package/src/client/EndpointTab.tsx +360 -0
- package/src/client/Modal.tsx +46 -0
- package/src/client/RouterView.tsx +186 -0
- package/src/client/SupplierDetail.tsx +943 -0
- package/src/client/index.tsx +18 -0
- package/src/client/router.css +2096 -0
- package/src/client/sidebar-entry.ts +167 -0
- package/src/client/workspace-controller.ts +26 -0
- package/src/client/workspace-mount.tsx +117 -0
- package/src/credential-store.ts +64 -0
- package/src/index.ts +468 -0
- package/src/keys.ts +143 -0
- package/src/llm/adapter.ts +273 -0
- package/src/router/index.ts +273 -0
- package/src/router/types.ts +85 -0
- package/src/shared.ts +152 -0
- package/src/supplier-config.ts +144 -0
- package/src/suppliers/contract.ts +73 -0
- package/src/suppliers/loader.ts +191 -0
- package/src/suppliers/nvidia/plugin.ts +268 -0
- package/src/suppliers/opencode/plugin.ts +200 -0
- package/src/suppliers/openrouter/plugin.ts +282 -0
- package/src/suppliers/registry.ts +365 -0
- package/src/types.d.ts +45 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** 通用凭证存储。 */
|
|
2
|
+
export declare class CredentialStore {
|
|
3
|
+
private db;
|
|
4
|
+
constructor(authDir: string);
|
|
5
|
+
/** 某供应商的所有凭证 uid。 */
|
|
6
|
+
list(supplierId: string): string[];
|
|
7
|
+
/** 读某凭证(不透明 blob)。不存在返回 undefined。 */
|
|
8
|
+
get<T = unknown>(supplierId: string, uid: string): T | undefined;
|
|
9
|
+
/** 存凭证(upsert)。 */
|
|
10
|
+
save(supplierId: string, uid: string, data: unknown): void;
|
|
11
|
+
/** 删凭证。 */
|
|
12
|
+
remove(supplierId: string, uid: string): void;
|
|
13
|
+
/** 关闭连接(可选)。 */
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Plugin identity for cordis.yml rows. */
|
|
2
|
+
export declare const name = "dsh-router";
|
|
3
|
+
/** Services required before mounting: the webserver (routes) + llm (设置-模型). */
|
|
4
|
+
export declare const inject: string[];
|
|
5
|
+
/** Plugin body. */
|
|
6
|
+
export declare function apply(rawContext: unknown): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** 库内一条 key。 */
|
|
2
|
+
export interface ApiKeyEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
/** 完整 key(仅创建时返回一次明文;列表返回脱敏)。 */
|
|
6
|
+
key: string;
|
|
7
|
+
isActive: boolean;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function generateKey(): string;
|
|
11
|
+
/** 密钥库。 */
|
|
12
|
+
export declare class KeysStore {
|
|
13
|
+
private fp;
|
|
14
|
+
private keys;
|
|
15
|
+
private require;
|
|
16
|
+
constructor(stateFile: string);
|
|
17
|
+
private load;
|
|
18
|
+
private save;
|
|
19
|
+
list(): Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
key: string;
|
|
23
|
+
masked: string;
|
|
24
|
+
isActive: boolean;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
}>;
|
|
27
|
+
/** 创建 key,返回明文(仅此一次)。 */
|
|
28
|
+
create(name: string): {
|
|
29
|
+
entry: ApiKeyEntry;
|
|
30
|
+
key: string;
|
|
31
|
+
};
|
|
32
|
+
remove(id: string): boolean;
|
|
33
|
+
setActive(id: string, isActive: boolean): boolean;
|
|
34
|
+
/** requireApiKey 开关。 */
|
|
35
|
+
get requireApiKey(): boolean;
|
|
36
|
+
set requireApiKey(v: boolean);
|
|
37
|
+
/** 校验 Bearer key:requireApiKey 关 → 放行;开 → 库内启用 key 或 TW2A_API_KEY env。 */
|
|
38
|
+
verify(bearer: string | undefined): boolean;
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH llm adapter for dsh-router(provider 固定卡片,名字 Router)。
|
|
3
|
+
*
|
|
4
|
+
* 模型目录 = 组合(自动带出,不可改)。对话转发到本插件 /v1/chat/completions,
|
|
5
|
+
* 由现有路由按组合策略命中供应商模型。
|
|
6
|
+
*
|
|
7
|
+
* 纯文本流:组合为文本模型,本 adapter 不处理图片附件。
|
|
8
|
+
*/
|
|
9
|
+
import { LlmAdapter, type GenerateOptions, type LlmModelInfo, type StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
10
|
+
/** 模型目录来源:组合。 */
|
|
11
|
+
export interface RouterAdapterSource {
|
|
12
|
+
comboModels: () => Promise<Array<{
|
|
13
|
+
id: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
}>>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* DSH adapter:provider `router`。模型目录 = 组合;stream 转发到本插件
|
|
19
|
+
* /v1/chat/completions(组合路由在 /v1 内完成)。
|
|
20
|
+
*/
|
|
21
|
+
export declare class RouterAdapter extends LlmAdapter {
|
|
22
|
+
private readonly baseURL;
|
|
23
|
+
private readonly source;
|
|
24
|
+
constructor(baseURL: string, source: RouterAdapterSource);
|
|
25
|
+
providerInfo(provider: string): {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
30
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ServerResponse } from 'node:http';
|
|
2
|
+
import type { Supplier, ChatRequest, ModelInfo, ModelWithEnabled, SupplierStatus, Combo } from './types.ts';
|
|
3
|
+
/** 路由器。 */
|
|
4
|
+
export declare class Router {
|
|
5
|
+
private suppliers;
|
|
6
|
+
private combosFp;
|
|
7
|
+
private customCombos;
|
|
8
|
+
/** round-robin 轮转游标(按组合 id 记忆)。 */
|
|
9
|
+
private rrCursors;
|
|
10
|
+
constructor(stateFile?: string);
|
|
11
|
+
private loadCombos;
|
|
12
|
+
private saveCombos;
|
|
13
|
+
add(supplier: Supplier): void;
|
|
14
|
+
/** 移除供应商(外部插件卸载时注销)。 */
|
|
15
|
+
removeSupplier(id: string): boolean;
|
|
16
|
+
/** 返回全部供应商状态(面板用)。 */
|
|
17
|
+
status(): {
|
|
18
|
+
suppliers: SupplierStatus[];
|
|
19
|
+
};
|
|
20
|
+
/** 供应商前缀信息(组合模型全名 = alias/id,展示时动态拼接)。 */
|
|
21
|
+
aliases(): Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
alias: string;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* OpenAI 兼容模型列表(/v1/models):组合(自动带出,不可改)+ 手动添加的模型。
|
|
28
|
+
* pi-ai 等 DSH provider 通过它发现 Router 的模型目录。
|
|
29
|
+
*/
|
|
30
|
+
listModels(): Promise<ModelInfo[]>;
|
|
31
|
+
/** 组合列表(面板用):用户自定义组合。 */
|
|
32
|
+
combos(): Promise<Combo[]>;
|
|
33
|
+
/** 可用模型(按供应商分组,仅启用),面板加模型用。 */
|
|
34
|
+
supplierModels(): Promise<Array<{
|
|
35
|
+
supplier: {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
alias: string;
|
|
39
|
+
};
|
|
40
|
+
models: ModelWithEnabled[];
|
|
41
|
+
}>>;
|
|
42
|
+
private validModels;
|
|
43
|
+
private validStrategy;
|
|
44
|
+
/** 组合模型统一存裸 id:剥掉 alias/ 前缀(前缀随供应商动态变)。 */
|
|
45
|
+
private normalizeModelIds;
|
|
46
|
+
/** 创建组合(name 唯一,非 default)。 */
|
|
47
|
+
createCombo(name: string, strategy: string, models: string[]): {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
error?: string;
|
|
50
|
+
combo?: Combo;
|
|
51
|
+
};
|
|
52
|
+
/** 更新组合(按 id)。 */
|
|
53
|
+
updateCombo(id: string, name: string, strategy: string, models: string[]): {
|
|
54
|
+
ok: boolean;
|
|
55
|
+
error?: string;
|
|
56
|
+
};
|
|
57
|
+
/** 删除组合(按 id,default 不可删)。 */
|
|
58
|
+
removeCombo(id: string): {
|
|
59
|
+
ok: boolean;
|
|
60
|
+
error?: string;
|
|
61
|
+
};
|
|
62
|
+
/** 按组合名查组合(含 `/` 的模型名不匹配)。 */
|
|
63
|
+
comboByName(name: string): Combo | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* 处理 /v1/chat/completions。
|
|
66
|
+
* - model 命中组合名 → 在组合模型里按策略选一个,交给供应商;失败按回退顺序尝试剩余模型。
|
|
67
|
+
* - 否则 → 依次尝试供应商,直到某个返回 true(已写响应)或全部返回 false。
|
|
68
|
+
*/
|
|
69
|
+
chatCompletions(req: ChatRequest, res: ServerResponse): Promise<void>;
|
|
70
|
+
/** 把 model 改写为组合选中的模型名后尝试供应商。 */
|
|
71
|
+
private chatWithModel;
|
|
72
|
+
dispose(): void;
|
|
73
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 供应商(supplier)抽象 —— dsh-router 路由器本体对接的外部通道。
|
|
3
|
+
* 每个供应商提供 OpenAI 兼容的 chat/models,并暴露面板状态。
|
|
4
|
+
* 仿 9router:供应商 = 一种"免费/付费"通道;dsh-router 在其上做路由。
|
|
5
|
+
*/
|
|
6
|
+
import type { ServerResponse } from 'node:http';
|
|
7
|
+
/** 面板展示的账号状态(脱敏)。 */
|
|
8
|
+
export interface SupplierAccount {
|
|
9
|
+
uid: string;
|
|
10
|
+
nickname?: string;
|
|
11
|
+
credits: number;
|
|
12
|
+
cooling: boolean;
|
|
13
|
+
until?: string;
|
|
14
|
+
reason?: string;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
err_count?: number;
|
|
17
|
+
}
|
|
18
|
+
/** 供应商面板状态。 */
|
|
19
|
+
export interface SupplierStatus {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
accounts: SupplierAccount[];
|
|
23
|
+
}
|
|
24
|
+
/** OpenAI 模型条目。 */
|
|
25
|
+
export interface ModelInfo {
|
|
26
|
+
id: string;
|
|
27
|
+
context_length?: number;
|
|
28
|
+
}
|
|
29
|
+
/** 面板展示的模型(含启用状态)。 */
|
|
30
|
+
export interface ModelWithEnabled extends ModelInfo {
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
/** 是否为自定义模型(可删除)。 */
|
|
33
|
+
custom?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** 组合策略:fallback = 按顺序尝试;round-robin = 轮转。 */
|
|
36
|
+
export type ComboStrategy = 'fallback' | 'round-robin';
|
|
37
|
+
/** 组合:命名的一组模型,路由时按策略(回退/轮询)命中其中一个。 */
|
|
38
|
+
export interface Combo {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
strategy: ComboStrategy;
|
|
42
|
+
models: string[];
|
|
43
|
+
}
|
|
44
|
+
/** 一次 /v1/chat/completions 请求。 */
|
|
45
|
+
export interface ChatRequest {
|
|
46
|
+
/** 原始请求体 JSON 字符串。 */
|
|
47
|
+
rawBody: string;
|
|
48
|
+
stream: boolean;
|
|
49
|
+
model: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 供应商接口。chatCompletions 直接向 res 写响应(流式 SSE 或 JSON),
|
|
53
|
+
* 成功返回 true;返回 false 表示"该供应商无健康账号可服务"(触发路由器
|
|
54
|
+
* 轮换到下一个供应商)。写响应后不得再改 res。
|
|
55
|
+
*/
|
|
56
|
+
export interface Supplier {
|
|
57
|
+
readonly id: string;
|
|
58
|
+
readonly name: string;
|
|
59
|
+
/** 优先级:数字越小越优先(免费通道优先)。 */
|
|
60
|
+
readonly priority: number;
|
|
61
|
+
/** 图标(URL 或 SVG data URI),面板供应商卡片显示。可选。 */
|
|
62
|
+
readonly icon?: string;
|
|
63
|
+
status(): SupplierStatus;
|
|
64
|
+
listModels(force?: boolean): Promise<ModelInfo[]> | ModelInfo[];
|
|
65
|
+
/** 面板展示的模型(含启用/自定义标记)。 */
|
|
66
|
+
modelsWithEnabled(): Promise<ModelWithEnabled[]> | ModelWithEnabled[];
|
|
67
|
+
/** 供应商前缀(模型完整名 = alias/id)。 */
|
|
68
|
+
getAlias(): string;
|
|
69
|
+
/** 用户手动添加的模型 id(可选,/v1/models 用它聚合自定义模型)。 */
|
|
70
|
+
customModelIds?(): string[];
|
|
71
|
+
chatCompletions(req: ChatRequest, res: ServerResponse): Promise<boolean>;
|
|
72
|
+
/** 测试模型(验证上游真实可用,必要差异化能力)。 */
|
|
73
|
+
testModel(id: string): Promise<{
|
|
74
|
+
ok: boolean;
|
|
75
|
+
error?: string;
|
|
76
|
+
}>;
|
|
77
|
+
/** 删除链接(清理凭证;内部能力,js 契约不要求实现)。 */
|
|
78
|
+
removeLink?(uid: string): Promise<boolean>;
|
|
79
|
+
dispose(): void;
|
|
80
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants and wire types for dsh-router.
|
|
3
|
+
*
|
|
4
|
+
* Pure values — no framework imports — so the host (tsdown ESM) and the
|
|
5
|
+
* client (vite CJS browser bundle) can both inline them safely.
|
|
6
|
+
*/
|
|
7
|
+
/** The sidebar entry / panel dataset attribute family name. */
|
|
8
|
+
export declare const ROUTER_ENTRY_SELECTOR = "[data-dsh-router-entry]";
|
|
9
|
+
/** The center-column takeover panel attribute. */
|
|
10
|
+
export declare const ROUTER_VIEW_SELECTOR = "[data-dsh-router-view]";
|
|
11
|
+
/** Active-state attribute on <html> that shows the panel. */
|
|
12
|
+
export declare const ROUTER_ACTIVE_ATTR = "data-dsh-router-active";
|
|
13
|
+
/** The panel's visible title (also the sidebar entry label). */
|
|
14
|
+
export declare const ROUTER_TITLE = "\u8DEF\u7531\u7CFB\u7EDF";
|
|
15
|
+
/** Same-origin API base the client panel fetches (host half serves it). */
|
|
16
|
+
export declare const ROUTER_API_BASE = "/router/api";
|
|
17
|
+
/** Bearer key env / credential reference protecting /v1/*. */
|
|
18
|
+
export declare const API_KEY_REF = "TW2A_API_KEY";
|
|
19
|
+
/** One account row from `/router/api/status`. */
|
|
20
|
+
export interface RouterAccount {
|
|
21
|
+
uid: string;
|
|
22
|
+
nickname?: string;
|
|
23
|
+
credits: number;
|
|
24
|
+
cooling: boolean;
|
|
25
|
+
until?: string;
|
|
26
|
+
reason?: string;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
err_count?: number;
|
|
29
|
+
/** 所属供应商 id。 */
|
|
30
|
+
supplier?: string;
|
|
31
|
+
}
|
|
32
|
+
/** `/router/api/status` response. */
|
|
33
|
+
export interface RouterStatusResponse {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
accounts?: RouterAccount[];
|
|
37
|
+
}
|
|
38
|
+
/** `/router/api/models` response. */
|
|
39
|
+
export interface RouterModelsResponse {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
error?: string;
|
|
42
|
+
models?: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
context_length?: number;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
/** `/router/api/health` response. */
|
|
48
|
+
export interface RouterHealthResponse {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
suppliers?: Array<{
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
/** 图标(URL 或 SVG data URI)。 */
|
|
54
|
+
icon?: string;
|
|
55
|
+
/** 面板能力集合(登录/签到/连接池/模型管理等,按存在性)。 */
|
|
56
|
+
capabilities?: string[];
|
|
57
|
+
/** 来源:内置 / 用户目录 / 外部插件。 */
|
|
58
|
+
source?: 'builtin' | 'user' | 'external';
|
|
59
|
+
}>;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
/** 组合策略。 */
|
|
63
|
+
export type RouterComboStrategy = 'fallback' | 'round-robin';
|
|
64
|
+
/** 组合。models = 该组合命中的模型列表(裸模型 id,展示时动态拼前缀)。 */
|
|
65
|
+
export interface RouterCombo {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
strategy: RouterComboStrategy;
|
|
69
|
+
models: string[];
|
|
70
|
+
}
|
|
71
|
+
/** `/router/api/combos` response. */
|
|
72
|
+
export interface RouterCombosResponse {
|
|
73
|
+
ok: boolean;
|
|
74
|
+
error?: string;
|
|
75
|
+
combos?: RouterCombo[];
|
|
76
|
+
/** 可用模型(按供应商分组,仅启用),面板加模型用。 */
|
|
77
|
+
groups?: RouterComboSupplierGroup[];
|
|
78
|
+
/** 供应商前缀信息(组合模型全名 = alias/id,展示时动态拼接)。 */
|
|
79
|
+
aliases?: RouterComboAlias[];
|
|
80
|
+
}
|
|
81
|
+
/** 组合页供应商前缀信息。 */
|
|
82
|
+
export interface RouterComboAlias {
|
|
83
|
+
id: string;
|
|
84
|
+
name: string;
|
|
85
|
+
alias: string;
|
|
86
|
+
}
|
|
87
|
+
/** 组合页可用模型分组(一个供应商一组)。 */
|
|
88
|
+
export interface RouterComboSupplierGroup {
|
|
89
|
+
supplier: {
|
|
90
|
+
id: string;
|
|
91
|
+
name: string;
|
|
92
|
+
alias: string;
|
|
93
|
+
};
|
|
94
|
+
models: RouterSupplierModel[];
|
|
95
|
+
}
|
|
96
|
+
/** 库内一条 key(列表含完整 key,客户端脱敏显示)。 */
|
|
97
|
+
export interface RouterKey {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
key: string;
|
|
101
|
+
masked: string;
|
|
102
|
+
isActive: boolean;
|
|
103
|
+
createdAt: string;
|
|
104
|
+
}
|
|
105
|
+
/** `/router/api/keys` response. */
|
|
106
|
+
export interface RouterKeysResponse {
|
|
107
|
+
ok: boolean;
|
|
108
|
+
error?: string;
|
|
109
|
+
keys?: RouterKey[];
|
|
110
|
+
}
|
|
111
|
+
/** `/router/api/settings` response. */
|
|
112
|
+
export interface RouterSettingsResponse {
|
|
113
|
+
ok: boolean;
|
|
114
|
+
error?: string;
|
|
115
|
+
requireApiKey?: boolean;
|
|
116
|
+
}
|
|
117
|
+
/** 供应商模型(含启用状态)。 */
|
|
118
|
+
export interface RouterSupplierModel {
|
|
119
|
+
id: string;
|
|
120
|
+
context_length?: number;
|
|
121
|
+
enabled: boolean;
|
|
122
|
+
/** 是否为自定义(手动添加 / 拉取)模型,可删除。 */
|
|
123
|
+
custom?: boolean;
|
|
124
|
+
}
|
|
125
|
+
/** `/router/api/suppliers/:id/models` response. */
|
|
126
|
+
export interface RouterSupplierModelsResponse {
|
|
127
|
+
ok: boolean;
|
|
128
|
+
error?: string;
|
|
129
|
+
/** 供应商前缀(模型全名 = alias/id)。 */
|
|
130
|
+
alias?: string;
|
|
131
|
+
models?: RouterSupplierModel[];
|
|
132
|
+
}
|
|
133
|
+
/** `/router/api/suppliers/:id/login` response. */
|
|
134
|
+
export interface RouterLoginResponse {
|
|
135
|
+
ok: boolean;
|
|
136
|
+
error?: string;
|
|
137
|
+
loginUrl?: string;
|
|
138
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type PoolStrategy = 'fallback' | 'round-robin';
|
|
2
|
+
export type CheckinRule = 'all' | 'first';
|
|
3
|
+
export interface SupplierConfig {
|
|
4
|
+
alias: string;
|
|
5
|
+
disabled: string[];
|
|
6
|
+
custom: string[];
|
|
7
|
+
poolOrder: string[];
|
|
8
|
+
poolStrategy: PoolStrategy;
|
|
9
|
+
checkinRule: CheckinRule;
|
|
10
|
+
}
|
|
11
|
+
/** 通用供应商配置存储。 */
|
|
12
|
+
export declare class SupplierConfigStore {
|
|
13
|
+
private fp;
|
|
14
|
+
private bySupplier;
|
|
15
|
+
constructor(stateFile: string);
|
|
16
|
+
/** 读取某供应商配置(不存在则返回默认并注册)。 */
|
|
17
|
+
get(supplierId: string): SupplierConfig;
|
|
18
|
+
setAlias(supplierId: string, alias: string): void;
|
|
19
|
+
setModelEnabled(supplierId: string, modelId: string, enabled: boolean): void;
|
|
20
|
+
setAllModelsEnabled(supplierId: string, enabled: boolean, allModelIds: string[]): void;
|
|
21
|
+
addCustomModel(supplierId: string, modelId: string): void;
|
|
22
|
+
removeCustomModel(supplierId: string, modelId: string): void;
|
|
23
|
+
setPoolOrder(supplierId: string, uids: string[]): void;
|
|
24
|
+
setPoolStrategy(supplierId: string, strategy: string): void;
|
|
25
|
+
setCheckinRule(supplierId: string, rule: string): void;
|
|
26
|
+
private load;
|
|
27
|
+
private saveLocked;
|
|
28
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用供应商契约 —— 供应商 js 只需 export 一个满足本契约的对象/类实例。
|
|
3
|
+
*
|
|
4
|
+
* 分工:
|
|
5
|
+
* - 通用能力(连接池排序/策略、模型启用/自定义、别名)由 dsh-router 核心
|
|
6
|
+
* SupplierConfigStore 统一管理,供应商 js 不需要实现。
|
|
7
|
+
* - 可插拔 js 模块只提供**差异化能力**:如何列模型/调上游/登录/签到等。
|
|
8
|
+
*
|
|
9
|
+
* 用户自定义供应商 js 放到 `~/.dsh/profiles/web/suppliers/*.js`,重启后自动加载。
|
|
10
|
+
* 内置供应商与用户 js 同构(差异化能力驱动)。
|
|
11
|
+
*/
|
|
12
|
+
import type { ServerResponse } from 'node:http';
|
|
13
|
+
import type { ChatRequest, ModelInfo, SupplierAccount, SupplierStatus } from '../router/types.ts';
|
|
14
|
+
import type { SupplierConfigStore } from '../supplier-config.ts';
|
|
15
|
+
import type { CredentialStore } from '../credential-store.ts';
|
|
16
|
+
/** 供应商配置:loader 注入的运行时环境。 */
|
|
17
|
+
export interface SupplierEnv {
|
|
18
|
+
/** 数据目录(state.json / keys.json 所在目录)。 */
|
|
19
|
+
dataDir: string;
|
|
20
|
+
/** 日志。 */
|
|
21
|
+
log: (msg: string) => void;
|
|
22
|
+
/** 通用配置存储(连接池/模型管理/别名/签到规则,核心统一管)。 */
|
|
23
|
+
store: SupplierConfigStore;
|
|
24
|
+
/** 通用凭证存储(账号凭证,核心统一管)。 */
|
|
25
|
+
credentials: CredentialStore;
|
|
26
|
+
}
|
|
27
|
+
/** 通用供应商工厂:一个 js 模块 export 它(或 default export 它),loader 调用得到实例。 */
|
|
28
|
+
export type SupplierFactory = (env: SupplierEnv) => SupplierModule;
|
|
29
|
+
/** 供应商模块 —— 契约(核心必须,差异化可选)。 */
|
|
30
|
+
export interface SupplierModule {
|
|
31
|
+
/** 供应商唯一 id(小写字母数字 - _)。 */
|
|
32
|
+
readonly id: string;
|
|
33
|
+
/** 显示名。 */
|
|
34
|
+
readonly name: string;
|
|
35
|
+
/** 优先级:数字越小越优先(免费通道优先)。 */
|
|
36
|
+
readonly priority?: number;
|
|
37
|
+
/** 图标(URL 或 SVG data URI),面板供应商卡片显示。可选。 */
|
|
38
|
+
readonly icon?: string;
|
|
39
|
+
status(): SupplierStatus;
|
|
40
|
+
/** 模型来源。`force=true` 时强制刷新来源(核心「获取模型」按钮调用)。 */
|
|
41
|
+
listModels(force?: boolean): Promise<ModelInfo[]> | ModelInfo[];
|
|
42
|
+
/** 默认前缀(可被通用层 alias 覆盖)。 */
|
|
43
|
+
getAlias(): string;
|
|
44
|
+
chatCompletions(req: ChatRequest, res: ServerResponse): Promise<boolean>;
|
|
45
|
+
/** 测试模型(验证上游真实可用,必要差异化能力)。 */
|
|
46
|
+
testModel(id: string): Promise<{
|
|
47
|
+
ok: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
}>;
|
|
50
|
+
dispose(): void;
|
|
51
|
+
/** 生成登录链接(加账号)。有它才显示连接池(账号池)。 */
|
|
52
|
+
generateLoginUrl?(): string | {
|
|
53
|
+
ok: boolean;
|
|
54
|
+
error?: string;
|
|
55
|
+
loginUrl?: string;
|
|
56
|
+
} | Promise<string | {
|
|
57
|
+
ok: boolean;
|
|
58
|
+
error?: string;
|
|
59
|
+
loginUrl?: string;
|
|
60
|
+
}>;
|
|
61
|
+
/** 回调完成登录。 */
|
|
62
|
+
completeLogin?(callbackUrl: string): Promise<{
|
|
63
|
+
uid: string;
|
|
64
|
+
nickname: string;
|
|
65
|
+
}>;
|
|
66
|
+
/** 添加 API key 账号(弹窗填名字+key,而非 URL 登录)。有它同样显示连接池。 */
|
|
67
|
+
addApiKey?(input: {
|
|
68
|
+
name: string;
|
|
69
|
+
apiKey: string;
|
|
70
|
+
}): Promise<{
|
|
71
|
+
ok: boolean;
|
|
72
|
+
error?: string;
|
|
73
|
+
account?: {
|
|
74
|
+
uid: string;
|
|
75
|
+
nickname: string;
|
|
76
|
+
};
|
|
77
|
+
}>;
|
|
78
|
+
/** 删除链接(清理凭证;内部能力)。 */
|
|
79
|
+
removeLink?(uid: string): Promise<boolean>;
|
|
80
|
+
/** 轮询式登录标记:有它 → 面板隐藏「粘贴回调链接」步骤,登录后直接点完成(后台轮询)。 */
|
|
81
|
+
pollLogin?(): boolean;
|
|
82
|
+
/** 触发签到。签到规则(all/first)是 dsh-router 通用策略,不在此列。 */
|
|
83
|
+
checkinNow?(): Promise<{
|
|
84
|
+
ok: boolean;
|
|
85
|
+
total: number;
|
|
86
|
+
succeeded: number;
|
|
87
|
+
already?: number;
|
|
88
|
+
error?: string;
|
|
89
|
+
results?: Array<{
|
|
90
|
+
uid: string;
|
|
91
|
+
ok: boolean;
|
|
92
|
+
status?: string;
|
|
93
|
+
message?: string;
|
|
94
|
+
}>;
|
|
95
|
+
}>;
|
|
96
|
+
}
|
|
97
|
+
/** 供应商账号(复用 router 类型,供 loader 包装)。 */
|
|
98
|
+
export type { SupplierAccount };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Supplier } from '../router/types.ts';
|
|
2
|
+
import type { SupplierConfigStore } from '../supplier-config.ts';
|
|
3
|
+
import type { CredentialStore } from '../credential-store.ts';
|
|
4
|
+
import type { SupplierEnv, SupplierModule } from './contract.ts';
|
|
5
|
+
/** 供应商加载错误(不阻断其他供应商)。 */
|
|
6
|
+
export interface SupplierLoadError {
|
|
7
|
+
id: string;
|
|
8
|
+
file: string;
|
|
9
|
+
error: string;
|
|
10
|
+
}
|
|
11
|
+
/** 供应商实例 + 能力标记。 */
|
|
12
|
+
export interface LoadedSupplier {
|
|
13
|
+
supplier: Supplier;
|
|
14
|
+
/** 差异化能力集合(方法名存在性,通用能力不在此列)。 */
|
|
15
|
+
capabilities: Set<string>;
|
|
16
|
+
/** 来源:内置 / 用户目录 / 外部插件(cordis service)。 */
|
|
17
|
+
source: 'builtin' | 'user' | 'external';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 从 SupplierModule 实例构造 LoadedSupplier(能力检测 + Supplier 包装)。
|
|
21
|
+
* 文件加载(loadOne)与外部插件供应商(cordis service)共用。
|
|
22
|
+
*/
|
|
23
|
+
export declare function wrapModule(instance: SupplierModule, env: SupplierEnv, source: string, kind?: 'builtin' | 'user' | 'external'): LoadedSupplier;
|
|
24
|
+
export interface LoadSuppliersOptions {
|
|
25
|
+
/** 内置供应商目录(<plugin>/lib/suppliers)。 */
|
|
26
|
+
builtinDir: string;
|
|
27
|
+
/** 用户供应商目录(~/.dsh/profiles/web/suppliers)。 */
|
|
28
|
+
userDir: string;
|
|
29
|
+
/** 数据目录。 */
|
|
30
|
+
dataDir: string;
|
|
31
|
+
/** 通用配置存储。 */
|
|
32
|
+
store: SupplierConfigStore;
|
|
33
|
+
/** 通用凭证存储。 */
|
|
34
|
+
credentials: CredentialStore;
|
|
35
|
+
log: (msg: string) => void;
|
|
36
|
+
}
|
|
37
|
+
/** 加载全部供应商(内置 + 用户,用户覆盖内置同 id)。 */
|
|
38
|
+
export declare function loadSuppliers(opts: LoadSuppliersOptions): Promise<{
|
|
39
|
+
suppliers: LoadedSupplier[];
|
|
40
|
+
errors: SupplierLoadError[];
|
|
41
|
+
}>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 供应商注册表 —— 持有加载的供应商 + 按能力暴露的通用端点。
|
|
3
|
+
*
|
|
4
|
+
* 通用能力(dsh-router 核心,所有供应商自动可用,js 无需实现):
|
|
5
|
+
* GET /suppliers/:id/models models + alias(listModels 合并启用状态)
|
|
6
|
+
* POST /suppliers/:id/models/toggle {id, enabled}
|
|
7
|
+
* POST /suppliers/:id/models/add {id}
|
|
8
|
+
* POST /suppliers/:id/models/remove {id}
|
|
9
|
+
* POST /suppliers/:id/models/bulk {enabled}
|
|
10
|
+
* POST /suppliers/:id/alias {alias}
|
|
11
|
+
* GET /suppliers/:id/pool/order + POST {uids}
|
|
12
|
+
* GET /suppliers/:id/pool/strategy + POST {strategy}
|
|
13
|
+
*
|
|
14
|
+
* 差异化能力(供应商 js 实现,按存在性注册;未实现返回 404):
|
|
15
|
+
* POST /suppliers/:id/models/fetch 拉取上游模型
|
|
16
|
+
* POST /suppliers/:id/models/test {id}
|
|
17
|
+
* POST /suppliers/:id/login 生成登录链接
|
|
18
|
+
* POST /suppliers/:id/login/callback {callbackUrl}
|
|
19
|
+
* POST /suppliers/:id/links/remove {uid}
|
|
20
|
+
* GET /suppliers/:id/checkin/rule + POST {rule}
|
|
21
|
+
* POST /suppliers/:id/checkin 触发签到
|
|
22
|
+
*/
|
|
23
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
24
|
+
import type { SupplierConfigStore } from '../supplier-config.ts';
|
|
25
|
+
import type { LoadedSupplier } from './loader.ts';
|
|
26
|
+
/** webServer 路由形状(与 index.ts 的 WebServerRoute 一致)。 */
|
|
27
|
+
export interface WebServerRoute {
|
|
28
|
+
kind: 'prefix' | 'exact';
|
|
29
|
+
path: string;
|
|
30
|
+
handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/** 为一个供应商生成端点(通用 + 差异化,返回 route 列表)。 */
|
|
33
|
+
export declare function supplierRoutes(base: string, loaded: LoadedSupplier, store: SupplierConfigStore): WebServerRoute[];
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-router-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DSH plugin: a simplified 9router (AI routing gateway). Adds a 路由系统 (Routing System) sidebar entry beside 记忆系统; the panel shows suppliers, account pool, combos, and endpoint/API keys.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/CARVIN94/dsh-router.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/CARVIN94/dsh-router#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/CARVIN94/dsh-router/issues"
|
|
13
|
+
},
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./client": "./lib/client.js",
|
|
22
|
+
"./src/*": "./src/*",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"dsh": {
|
|
29
|
+
"bundle": {
|
|
30
|
+
"patch": "./cordis.patch.yml"
|
|
31
|
+
},
|
|
32
|
+
"client": {
|
|
33
|
+
"inject": [
|
|
34
|
+
"react",
|
|
35
|
+
"react-dom",
|
|
36
|
+
"cordis"
|
|
37
|
+
],
|
|
38
|
+
"platform": "web"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"lib/index.js",
|
|
43
|
+
"lib/client.js",
|
|
44
|
+
"lib/client-registry.js",
|
|
45
|
+
"lib/types/**/*.d.ts",
|
|
46
|
+
"src",
|
|
47
|
+
"cordis.patch.yml",
|
|
48
|
+
"README.md",
|
|
49
|
+
"LICENSE"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown && npm run build:client",
|
|
53
|
+
"build:client": "DSH_CLIENT_ID=dsh-router DSH_CLIENT_FILE=client.js vite build -c client/vite.config.ts && DSH_CLIENT_ID=dsh-external/dsh-router DSH_CLIENT_FILE=client-registry.js vite build -c client/vite.config.ts",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"bundle": "tsdown",
|
|
56
|
+
"watch": "tsdown --watch"
|
|
57
|
+
},
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
61
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
62
|
+
"cordis": "^4.0.0-rc.7",
|
|
63
|
+
"react": "^18.2.0",
|
|
64
|
+
"react-dom": "^18.2.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"cordis": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@types/node": "^24.0.0",
|
|
73
|
+
"@types/react": "~18.3.1",
|
|
74
|
+
"@types/react-dom": "~18.3.1",
|
|
75
|
+
"cordis": "^4.0.0-rc.7",
|
|
76
|
+
"react": "^18.2.0",
|
|
77
|
+
"react-dom": "18.2.0",
|
|
78
|
+
"tsdown": "^0.22.2",
|
|
79
|
+
"typescript": "^5.6.0",
|
|
80
|
+
"vite": "8.1.3",
|
|
81
|
+
"vite-plugin-css-injected-by-js": "^3.5.2"
|
|
82
|
+
},
|
|
83
|
+
"publishConfig": {
|
|
84
|
+
"registry": "https://registry.npmjs.org"
|
|
85
|
+
}
|
|
86
|
+
}
|