dsh-starbridge-client — 星桥 StarBridge
星桥(StarBridge)AI 中台的 DSH 客户端插件。在自己的 DSH profile 里装这一个包, 就得到统一网关接入、填写地址或用平台账号登录、以及对话 / 反馈 / 知识库工具, 外加 Web UI 里的对话面板与设置页;再打开"模型路由",DSH 的每一次 AI 调用都经星桥网关。
插件对所有人发布,因此不含任何一家的地址或凭据:装上是空配置,填一次地址即可。
它是一个组合包(bundle),同时提供 host 面与 client 面:
| 面 | 入口 | 职责 |
|---|---|---|
| host | lib/index.js |
ctx.starBridge 服务、四个工具、/starbridge/api/* HTTP 路由、接入配置与凭据管理、OIDC PKCE |
| client | lib/client.js |
main 对话面板、settings.section 设置页(接入配置 / 登录 / 模型路由)、反馈条 |
分工只有一条:host 做脏活,client 做展示。浏览器不持有 access token,也不直接访问网关—— 所有网络请求、认证、重试、流式解析都在 host 面完成。
目录结构
dsh-starbridge-client/
├── package.json # dsh.bundle / dsh.client / exports
├── cordis.patch.yml # 向 host 组合插入插件行(组合包层),并声明模型面路由
├── tsconfig.json # host 编译(tsc → lib/*.js + lib/*.d.ts)
├── tsconfig.client.json # client 类型检查(jsx: react-jsx,noEmit)
├── tsdown.config.ts # client 打包(→ lib/client.js,自注册 classic script)
├── scripts/verify.mjs # 离线冒烟验证(无网络、无凭据)
└── src/
├── index.ts # host 入口:name / inject / Config / apply / 模型路由操作
├── augment.ts # ctx.starBridge 与 starBridge/feedback 的声明合并
├── config.ts # Config Schema + 加载期校验(响亮报错)
├── auth.ts # OIDC Authorization Code + PKCE、令牌与加密落盘(SealedJsonFile)
├── gateway-config.ts # 用户填写的接入配置:URL 规范化 + 落盘($DSH_HOME/starbridge-gateway.json)
├── gateway-access.ts # 凭据层:访问密钥 / 平台令牌(含到期前自动续期)+ 探活与登录
├── gateway.ts # 统一 API 客户端:身份头、trace_id、重试、SSE
├── service.ts # host 组合根(tools、路由、模型路由的唯一实现)
├── tools.ts # starbridge_chat / _feedback / _kb_query / _gateway
├── http.ts # /starbridge/api/* 路由(NDJSON 流、错误信封)
├── feedback-store.ts # 本地反馈记录 + 云端转发
├── errors.ts # 错误分类与凭据脱敏
├── trace.ts # trace_id 生成
├── log.ts # 日志脱敏
├── shared/ # 双端共享(protocol / markdown)
└── client/ # 浏览器面
├── index.tsx # 入口:注册三个 slot
├── api.ts # 只调用本插件 host 路由
├── ChatPanel.tsx # 对话面板(流式 + Markdown + 代码高亮)
├── SettingsPanel.tsx
├── FeedbackBar.tsx # 点赞 / 点踩 / 修正回答(问题所在 + 期望答案)
├── MarkdownView.tsx
├── highlight.ts # 零依赖代码高亮
├── theme.ts # 使用 --dsw-* 主题变量
├── login.ts # 登录入口
└── types.ts # slots 服务的最小声明
构建
npm install
npm run build # = build:host + build:client
npm run build:host # tsc -p tsconfig.json → lib/*.js + lib/*.d.ts
npm run build:client # tsdown → lib/client.js
npm run typecheck # 双端类型检查
npm run verify # 离线冒烟验证(195 项检查)
两个构建都必须先通过再安装:host 的 tsc 会真正产出 lib/index.js,
client 的 tsdown 会产出被 shell 当 classic script 加载的 lib/client.js。
安装
# 从插件市场「设置 → 插件市场」一键安装,或直接:
dsh plugin --profile web add dsh-starbridge-client
# 本地开发时从目录装(会在 profile 里建立 pnpm 链接)
dsh plugin --profile web add ./dsh-starbridge-client
# 确认组合结果(无需启动即可看到插件行)
dsh --profile web --dump-config | grep -A 20 starbridge
# 重启 profile
dsh web
dsh plugin add 会把依赖装进 profile,并把声明了 dsh.bundle 的包追加进
dsh.profile.bundles;该包的 cordis.patch.yml 因此成为组合层的一层。
装完插件还没有接上任何网关——出厂配置里的地址是空的,这是刻意的(见「配置」一节)。
打开 设置 → 星桥 StarBridge 填一次地址就通了;在那之前 starbridge_* 工具返回
[GATEWAY_NOT_CONFIGURED] 并告诉你去哪里填,而不是让你去翻日志。
接入:填地址 + 填密钥或登录(设置页)
**「设置 → 星桥 StarBridge」**是这一步的唯一入口,不需要改任何配置文件:
- 填服务地址:服务根、
/starbridge、/starbridge/gw、/starbridge/gw/v1四种粘贴形式都能识别并规范化;页面会显示归一化后的机器面与模型面地址。 - 二选一登录:
- 访问密钥:粘贴中台签发的 Access Key。写入 DSH 凭据库
(引用名
STARBRIDGE_GATEWAY_API_KEY,与模型路由的apiKeyEnv同名),不写配置文件。 这份凭据库里的副本就是它唯一的持久副本:DSH 重启后插件从同一个引用读回来, 所以重启不会丢密钥、也不会留下"路由指向星桥但没有凭据"的坏状态。 - 平台账号:用星桥控制台账号密码登录,换一枚平台 JWT(后端
POST /starbridge/gw/login)。勾选"在本机记住"后,凭据以 AES-256-GCM 加密落盘 ($DSH_HOME/starbridge-gateway-session.enc,密钥同为0600的starbridge-gateway-session.key),令牌到期前自动续期;随时可"清除本机保存的星桥凭据"。
- 访问密钥:粘贴中台签发的 Access Key。写入 DSH 凭据库
(引用名
- 模型路由开关:打开后,插件把
llm-pi-ai的starbridge路由baseURL指向<地址>/gw/v1,把凭据写进该路由的apiKeyEnv,并把新会话默认模型切到starbridge / general。关掉即退回 DSH 当前默认提供方。
连接过程返回逐步清单(地址 → 可达 → 凭据 → 模型路由),每一步独立报告成败与修法, 而不是笼统的"失败"。
非机密配置存放在 $DSH_HOME/starbridge-gateway.json:
{
"baseUrl": "https://starbridge.example.com/starbridge",
"userId": "liheng",
"department": "engineering",
"authMode": "account",
"modelProvider": "starbridge",
"model": "general",
"routeModelsThroughGateway": true,
"accountName": "liheng"
}
配置
所有部署参数都在 Config Schema 里,且都有可用默认值。改配置的两种方式:
- profile 自己的
$DSH_HOME/profiles/<profile>/cordis.patch.yml(在 bundle 层之后应用,覆盖单行配置); - 环境变量注入密钥(推荐,密钥不落盘):
STARBRIDGE_GATEWAY_URL、STARBRIDGE_GATEWAY_API_KEY、STARBRIDGE_OIDC_ISSUER_URL、STARBRIDGE_OIDC_CLIENT_ID、STARBRIDGE_OIDC_CLIENT_SECRET、STARBRIDGE_USER_ID、STARBRIDGE_DEPARTMENT。
主要字段:
| 字段 | 默认值 | 说明 |
|---|---|---|
gateway.gatewayUrl |
''(空) |
网关基址。出厂为空是刻意的:插件对所有人发布,不写死任何一家的地址;空地址不是加载错误,只是还没配置,工具会回 [GATEWAY_NOT_CONFIGURED]。被设置页填写的地址覆盖,也可用 STARBRIDGE_GATEWAY_URL 注入 |
gateway.apiKey |
'' |
机器凭据;优先用环境变量,也会被设置页填写的密钥覆盖 |
gateway.timeoutMs |
30000 |
单次请求超时 |
gateway.maxRetries |
2 |
仅对传输错误与 5xx 重试 |
gateway.modelProvider |
starbridge |
模型路由开关管理的 provider 路由名(必须与组合声明一致) |
oidc.issuerUrl / oidc.clientId |
'' |
为空则不启用 SSO;SSO 仍然可用,与两种新登录方式并存 |
identity.userId / department / scenario |
'' / '' / chat |
随每个请求上送的身份与场景;设置页填写的身份优先 |
behavior.useKnowledgeBase |
false |
是否默认请求知识库增强 |
behavior.forwardFeedback |
true |
false 则反馈只留在本机 |
storage.directory |
'' |
加密会话保险库目录;空则用 DSH home |
OIDC 客户端:网关侧需要注册一个 Public client + Authorization Code + PKCE,
回调地址为 http(s)://<dsh-host>/starbridge/api/login/complete(可用
STARBRIDGE_REDIRECT_URI 显式指定)。刷新令牌以 AES-256-GCM 加密存放于
<DSH home>/starbridge-session.enc,密钥为同目录下 0600 的 starbridge-session.key;
host 重启后仍是登录态。
工具
| 工具 | 用途 |
|---|---|
starbridge_chat |
发送对话,返回完整回复(内部走流式)。带 scenario / use_knowledge_base 参数 |
starbridge_feedback |
记录点赞 / 点踩 / 修正(含期望答案 expectation):本地必写,云端转发尽力而为 |
starbridge_kb_query |
查询公司知识库(网关支持时),返回带引用的片段 |
starbridge_gateway |
配置与查看接入:status 报告当前配置;save 保存地址与身份;use_access_key 保存用户粘贴的密钥;login 用平台账号登录;route_models 开关模型路由;forget 清除本机凭据 |
错误处理约定:需要模型能绕开的失败(未登录、网关不可达、知识库未开)以
{ ok: false, error } 返回而不是抛出,因此不会打断整轮对话;错误文本自带
[CODE] 说明 + Hint,模型可以直接转述给用户。
模型面:让全部对话经过星桥中台
上面那张表是工具面:只有模型主动调用 starbridge_* 工具时才经过中台。
本组合包同时接入模型面——把 DSH 的模型调用本身接到中台的 OpenAI 兼容面,
于是对话、子代理、摘要、标题生成每一次模型调用都先过中台。
组合层(cordis.patch.yml)声明两件事:
- 给
@deepseek-ai/dsh-llm-pi-ai声明一条手工网关路由starbridge(api: openai-completions)——注意不写baseURL:地址是每个部署自己的事实; - 该路由的
apiKeyEnv: STARBRIDGE_GATEWAY_API_KEY——设置页把密钥或平台令牌写在同一个引用下。
运行时接管由插件完成(用户点开关即可):把路由 baseURL 写进 llm-pi-ai 设置分节
(该分节按请求读取,因此无需重启)、把凭据写进凭据引用、把默认模型切到目标路由。
model 字段承载的是场景(general / code_review / doc_qa / summarize),不是具体上游模型:
上游由中台按「场景 + 部门」的路由规则决定,调用方无权指定——否则就能绕过路由与配额。
模型路由默认是关的。 装完插件、不点开关,你的默认模型仍然是你原来的提供方——
一个公开发布的插件不该在装上的一瞬间接管所有人的模型调用。点开开关(或走完设置页的
「连接」)之后才会切到 starbridge / general。
要求「装上即走中台」的部署,在自己的 profile cordis.patch.yml
(bundle 层之后应用)里补回这两段即可恢复出厂接管:
- id: llm-pi-ai
config:
providers:
starbridge:
baseURL: https://starbridge.example.com/starbridge/gw/v1
- id: agent-default-model
config: { provider: starbridge, model: general }
身份:逐人归因
用平台账号登录时,身份来自令牌自身(中台解析 claims 并重查用户),调用方无法伪造成别人——
这是逐人配额与审计真正成立的方式,也不需要把 x-user-id 写进任何 profile 文件。
若仍用访问密钥,可在设置页填身份标识,或在该 profile 自己的 cordis.patch.yml
(在 bundle 层之后应用)里补 headers:
- id: llm-pi-ai
config:
providers:
starbridge:
headers: { x-user-id: your.login }
或者由服务端配置兜底服务身份(环境变量 STARBRIDGE_GATEWAY_DEFAULT_USER)。
⚠️ 默认模型的优先级
agent-default-model 的设置分节是实时真源。桌面版首次运行会往 settings.yaml
写入 agent-default-model,它会覆盖组合层里的值——这正是设置页的
「模型路由」开关能生效的原因:它直接改这个设置分节,而不是要求用户手改文件。
本插件出厂不写这个分节(见上),所以装完不会改变你当前的默认模型。
路由一旦生效,中台不可达就等于没有模型可用——这是「所有对话经中台」的必然代价, 也是强制走中台的部署想要的效果。设置页关掉开关即可退回 DSH 当前默认提供方。
后端需要什么
中台需要暴露 OpenAI 兼容面(/starbridge/gw/v1/models 与
/starbridge/gw/v1/chat/completions),并接受 Authorization: Bearer
(标准 OpenAI 客户端只发这一种,不认 x-api-key),以及提供账号登录端点
POST /starbridge/gw/login(返回 {code:0,data:{accessToken,expiresIn,username,…}})。
契约见 gin-vue-admin-licensed/aiDoc/frontend-backend/starbridge-openai-face.md
与 starbridge-gateway-face.md。
HTTP 路由(浏览器面使用)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /starbridge/api/status |
生效配置 + 登录状态 + 模型路由状态 |
| POST | /starbridge/api/connectivity |
连通性测试(可传候选 gatewayUrl) |
| POST | /starbridge/api/chat |
NDJSON 帧流:delta / done / error |
| POST | /starbridge/api/feedback |
写入反馈 |
| POST | /starbridge/api/gateway/settings |
保存地址与身份 |
| POST | /starbridge/api/gateway/access-key |
用访问密钥接通(返回逐步清单) |
| POST | /starbridge/api/gateway/login |
平台账号登录(返回逐步清单) |
| POST | /starbridge/api/gateway/model-route |
开关模型路由 |
| POST | /starbridge/api/gateway/forget |
清除本机保存的凭据 |
| POST | /starbridge/api/login |
返回 OIDC 授权 URL |
| GET/POST | /starbridge/api/login/complete |
OIDC 回调 |
| POST | /starbridge/api/logout |
退出 OIDC 登录 |
验证
npm run verify
离线(无网络、无凭据、无 DSH 进程)跑 201 项检查,覆盖:组合包结构与 patch 组合(用 DSH 真实的
applyEntryPatches 算法)、Config 默认值、未配置安装也能加载(空地址不是加载错误)与非法配置的响亮拒绝、
三条冒烟用例、网关协议细节
(身份头 / trace / SSE 多种帧形 / 5xx 重试与 401 不重试)、全部 HTTP 路由
(含接入配置、两种登录方式、模型路由开关、重启后凭据回读)、client bundle 的自注册外壳
与 require 面、Markdown 解析与代码高亮。
验证会把 DSH_HOME 指向一个临时目录,因此不会读写你真实的 profile。
三条冒烟用例:
- A 正常对话 —
starbridge_chat流式返回并按序拼装,请求带x-user-id/x-department/x-scenario/x-trace-id; - B 未登录报错 — 工具返回
[AUTH_REQUIRED]与登录指引,不抛异常;反馈仍本地落库; - C 反馈写入成功 — 本地 session 事件 + 云端转发 accepted,并发出
starBridge/feedback事件。
清理与生命周期
工具的注册、HTTP 路由的注册、会话事件监听、服务与网关的拆除全部挂在插件 fiber 上:
ctx.tools.register 与 ctx.provide 的返回值由 Cordis 托管,路由注册包在 ctx.effect 里,
网关的在途请求由 ctx.effect 的清理函数统一 abort。因此 dsh plugin remove 或 profile
重载后,不会残留路由、监听器或未结束的上游请求。
安全
- 不在浏览器持有 access token;不以明文写入任何凭据;
- 访问密钥存进 DSH 凭据库(
$DSH_HOME/.credentials.yaml),平台令牌 AES-256-GCM 加密落盘, 两者都不进starbridge-gateway.json; - 设置页的密钥与密码输入是只写的:host 从不回传它们,页面也无从回显;
- 平台账号登录失败时后端只回"账号或密码不正确",不区分用户是否存在(避免账号枚举);
- 日志统一走脱敏 sink,
Bearer/ JWT /*_token/api_key形状的文本一律替换; - 网关返回与用户输入都按不可信文本渲染,Markdown 渲染器不解释原始 HTML(无
innerHTML); - 表单/流式的错误信封只暴露 code / message / hint,不回显上游响应体全文。
No comments yet. Be the first to write one.