Skip to content

hiwork-core

Verified

hiwork-core · v0.0.41 · UNLICENSED · Web UI

HiWork 企业统一登录与模型自动供给插件(ai-router 网关登录,Web 表单,无 CLI 认证)

Install

dsh plugin add hiwork-core

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Readme

hiwork-core

HiWork 的企业统一登录与模型自动供给插件,基于 DeepSeek Harness(DSH)插件机制实现,随 HiWork 桌面端打包分发。

员工在登录页用企业 LDAP 账号登录企业 AI 网关(ai-router),插件自动拉取该账号绑定的虚拟 Key 与预算内模型,写入凭据库与 llm-pi-ai 配置 —— Models 页即刻出现该员工可用的模型,请求经网关统一计费。全程零手动模型配置,登录面为 Web 表单(无 CLI 认证)。

架构一瞥

login gate / settings card(client)──credentials.set / settings.mutate──▶ Host onChange(server)
      ▶ POST /api/admin/auth/login → GET /api/me/keys + GET /api/me/key?key_id=
      ▶ 写 AI_ROUTER_API_KEY + session 凭据,写 llm-pi-ai.providers.<协议路由>
      ▶ pi-ai 热重载 → Models 页出现模型 → 请求走网关

复用 @deepseek-ai/dsh-llm-pi-ai 的手工路由,不新写 LLM 适配器。

多协议路由(协议随模型走)

网关按「入站协议 + 模型」匹配模型映射(GET /api/me/key 的 model_mapping),而 pi-ai 的 api 是路由级的 —— 因此插件把预算内模型按协议拆到路由,每路由一个协议、baseURL 按协议取对。路由在模型选择器里的分组标题用「线路C/M/R」,后缀即该路由讲的协议首字母:

路由 展示名 协议 baseURL 后缀 说明
providers.ai-router-chat 线路C openai-completions …/v1(SDK 追加 /chat/completions) chat/completions,兜底协议
providers.ai-router-anthropic 线路M anthropic-messages 裸 base Anthropic SDK 自带 v1/ 前缀
providers.ai-router 线路R openai-responses …/v1(SDK 追加 /responses) key 与协议保持不变,兼容 agent-default-model: provider: ai-router
  • 归属规则:精确 EqualFold 优先、*/? 通配(裸 * 兜底不参与归属);模型进入每一条映射命中的线路 —— 按协议分组、不做唯一归属,同一模型可以同时出现在多条线路里(例如同时声明 responses 与 anthropic 映射的模型,线路R 和线路M 都列出它),从哪条线路选它,请求就走那条线路的协议;无任何映射命中的模型落兜底线路C(网关的裸 * 兜底映射通常声明在 chat/completions 下,用 responses 入站反而匹配不到映射)。
  • 好处:DSH 里切换模型自动落到对应协议的路由;responses 系模型经 https://aigateway.hivery.cn/v1/responses;anthropic 系(如 k3/k3-256k/kimi-for-coding)不再 404(/v1/v1/messages)也不用手动整路切协议;只声明 chat/completions 映射的模型落线路C,不再被塞进 responses 入站。
  • 空线路不写入 profile(写入侧同时清掉该路由,旧版独立的 ai-router-responses 路由也会被清理)。
  • 官方 DeepSeek 提供方在本 bundle 内停用:cordis.patch.yml 用 - id: llm-deepseek + disabled: true 关掉 deepseek-official。官方适配器讲 DeepSeek 私有协议(请求体带 thinking、响应读 reasoning_content、图片走 Files API),网关只实现 OpenAI/Anthropic 兼容协议,改 baseURL 也对接不上,留着只会让模型选择器多出一个必然失败的分组。需要保留官方的部署,在 profile 的 cordis.patch.yml 里写同 id + disabled: false 覆盖(profile 层优先级高于 bundle 层)。
  • 官方 web 搜索同样停用:web-search-deepseek 也走 DEEPSEEK_API_KEY(企业用户没有),故 - id: web-search-deepseek + disabled: true 停用提供方(设置页「Web 搜索」卡片随命名空间消失),并把 tool-web 的 search 关掉,只保留不依赖凭据的 web_fetch。
  • 默认模型校正:agent-default-model 的组合默认值指向官方提供方(deepseek-official/deepseek-v4-flash)。供给成功后(登录/刷新)校正一次:当前选择仍可路由(本插件线路内查表,或其它已注册提供方 + 模型仍在列)就保持不变,否则落到兜底线路C 的第一个模型,避免新会话第一次请求就 UNKNOWN_MODEL。只写用户层、不写 composition,用户自己选的模型只要仍可用就不会被覆盖。

登录门禁

  • 打开即检测登录(硬门禁):页面加载后检测登录状态,未登录时全屏登录窗覆盖整个应用(不可交互、不可聚焦,防隐私泄漏),登录成功才放行;不可跳过。
  • 登录页为 HiWork 品牌视觉:左侧品牌标语「智联万物,从智联工作开始。」,右侧企业账号登录卡。
  • 网关地址固定为 https://aigateway.hivery.cn(无需、也不提供用户填写)。
  • 设置页「AI Router 网关」为一级 tab,可刷新模型 / 退出登录。
  • 多虚拟 KEY 支持:一个账号绑定多把虚拟 Key 时,已登录页显示 KEY 选择器(掩码明文 + 团队名),切换后自动按新 Key 重拉额度与模型。
  • 已下架模型自动隐藏:网关 /api/me/key 的 model_status 为 inactive(目录已下架、不可调用)时,插件直接隐藏该模型——额度展示列表与 Models 页可用模型均不含它;重新上架后下次刷新自动恢复。旧网关缺省该字段,行为不变。

分发与开发

本插件为 HiWork 内部项目,随桌面端离线打包,版本在 hiwork-desktop/src/bundled-plugins.ts 的 BUNDLED_PLUGINS 中锁定;scripts/publish-to-desktop.mjs 负责把构建产物同步进桌面端离线仓库。

要求 Node.js 22+ 与 pnpm:

pnpm install
pnpm build       # host(tsc + host-build)+ client(tsc + esbuild 单文件 bundle)
pnpm test        # vitest

客户端 bundle 由 scripts/client-build.mjs 用 esbuild 打成单文件:@deepseek-ai/* 与 react 走宿主模块表 external,品牌图片内联为 data URL。

Feature Center(扩展注册面)

hiwork-core 的 client 半边提供 cordis 服务 hiworkFeatureCenter,把左侧 HiWork 菜单与中央内容区做成可复用机制:业务插件(hiwork-automation、未来的 hiwork-knowledge)只注册一个 feature,即可出现在菜单里并在中央区独立渲染, 不需要再碰侧栏 DOM 或中央区状态机。

interface HiWorkFeature {
  readonly id: string                        // 稳定 id,同时写入 data-hiwork-active-page
  readonly label: () => string               // 函数形式,跟随语言切换
  readonly icon?: string                     // 内联 SVG 字符串
  readonly order?: number                    // 缺省 1000,按 order → id 稳定排序
  readonly render: (context: FeatureRenderContext) => React.ReactNode
}
interface FeatureRenderContext {
  readonly openNativeSession: (sessionId: string) => void  // 先退出 takeover,再交给宿主
  readonly openNativeHome: () => void
}
interface HiWorkCoreClientFace {
  readonly featureRegistry: FeatureRegistry  // register / list / activeId / open / openNative / subscribe
  readonly openFeature: (id: string) => boolean
  readonly openNativeSession: (sessionId: string) => void
  readonly openNativeHome: () => void
}

业务插件按服务名发现本面(不 runtime-import 本包的值):

ctx.inject(['hiworkFeatureCenter'], (scope) => {
  const face = scope.get('hiworkFeatureCenter') as HiWorkCoreClientFace
  ctx.effect(() => face.featureRegistry.register({
    id: 'knowledge',
    label: () => '知识库',
    order: 30,
    render: (context) => React.createElement(KnowledgeView, { openSession: context.openNativeSession }),
  }), 'hiwork-knowledge: feature center')
})

机制要点(实现见 src/client/feature-registry.ts 与 src/client/feature-center.ts):

  • 侧栏菜单注入:隐藏原生「新会话」按钮(保留 DOM 与 click handler,只加隐藏属性), 在同位置插入 HiWork 菜单;首项「新会话」直接代理原生按钮(图标克隆原生按钮的 SVG), 其余项按 order 排序。icon 是内联 SVG 字符串;缺省用兜底图标,保证侧栏折叠成 56px 图标栏时每个入口仍有可辨识图形(折叠态由宿主加在侧栏根上的 *_collapsed 类驱动纯 CSS:隐藏文字、图标放大到 18px,与原生控件同形)。 折叠态悬停 500ms 弹出提示气泡,黑底白字、圆角与字号逐项对齐框架 ui-primitives 的 Tooltip(--dsw-alias-tooltip-bg),取代浏览器自带的浅色 title 提示; 展开态文字已可见,与原生控件一致(Tooltip disabled={wide})不出气泡。 找不到原生按钮时不抛错、不隐藏任何入口,菜单只列 feature(安全降级)。
  • 中央区 takeover:挂一个 [data-hiwork-feature-root] React root,用 html[data-hiwork-active-page] 上的纯 CSS 规则隐藏原生会话子树(保持挂载、保留状态); 点击任意原生会话行或调用 openNativeSession 时,先卸载 root、移除属性恢复原生内容, 再交给宿主。切换 feature 先卸载旧 root,任意时刻只有一个业务页面。 takeover root 自带 background: var(--dsw-alias-bg-base)——与原生会话根同一个主题变量, 否则会透出框架灰底、与侧栏同色而不是原生页面的底色。
  • 自愈与清理:MutationObserver + 有界重试处理延迟挂载、语言切换与壳重建; render 抛错由错误边界捕获并恢复原生会话区;注入节点带实例令牌,HMR/重复 apply 不会出现两份菜单,旧实例的 dispose 也不会删掉新实例的节点。
  • Host 纯度:全部 DOM 逻辑只在 src/client/**,Host build 不引用 document。

目录结构

  • src/index.ts:宿主入口,登录编排、凭据写入与依赖接口。
  • src/client/feature-registry.ts:扩展注册表(排序、校验、活动页)。
  • src/client/feature-center.ts:侧栏菜单注入 + 中央区 takeover。
  • src/client/contracts.ts:业务插件可见的 client 面与服务名。
  • src/orchestration.ts:登录后模型供给编排(拉 Key、写路由、热重载)。
  • src/identity.ts:当前登录身份的只读 host 服务(hiworkIdentity)—— 业务插件(hiwork-memory 的记忆隔离键等)经 ctx.get('hiworkIdentity') 取 { loggedIn, subject, username, displayName, employeeName, teamName };subject 是 账号名的 sha256 前 16 位(u_ 前缀,不含账号原文),未登录回 { loggedIn: false }。
  • src/memory-config.ts:记忆系统配置下发的只读 host 服务(hiworkMemory)—— hiwork-memory 经 ctx.get('hiworkMemory').config() 取网关下发的 panelUrl / serviceId / teamIds / apiKey(网关 GET /api/me/memory-config;snake_case → camelCase 的转换只在 Host 做), 成功结果 5 分钟内存缓存、force 可绕过;凭据只在内存里活、不落盘、不进日志。
  • src/default-model.ts:供给成功后校正 agent-default-model(默认模型落点)。
  • src/client/login-gate.tsx:全屏登录门禁页。
  • src/client/section.tsx:设置页「AI Router 网关」卡片。
  • src/client/user-badge.tsx:已登录身份与 KEY 选择器。