# Wujie AI Business Package

`@wujie-shell/business` 是轻量的无界云端基础业务客户端包。它封装账号认证、当前用户信息、API Key 获取、模型、Skill 市场、计费、模板、通用 HTTP 配置和 API 路由常量；不承载产品 UI 流程、页面状态或组件类型。

Shell runtime 与 business 包的边界：

- `apps/shell/packages/shell-core`：Electron main/preload、OpenClaw runtime、gateway 生命周期、生成配置和打包接入。
- `apps/shell/packages/shell-sdk`：renderer 可用的 `window.wujieShell` 类型、工具函数和 React hooks。
- `apps/shell/packages/wujie-ai-business`：轻量无界账号、模型、计费、Skill 市场、模板客户端与 API 路由常量。
- `apps/desktop-cn/src/api/businessClient.ts`：产品 UI 创建 business client 的适配入口。
- `apps/desktop-cn/src/api/authClient.ts`、`billingClient.ts`、`templateClient.ts` 等：产品 UI 类型转换、默认参数补充和页面调用适配。

## 设计原则

- 调用方决定环境、接口域名、存储、`fetch` 实现和产品默认参数。
- 业务包不读取 `import.meta.env`、`process.env`、Electron 配置或 UI 全局状态。
- 业务包只消费调用方传入的 `env`、`endpoint`、`storage`、`fetch` 和可选 `logger`。
- token、user-id 和 device_id 默认按 `storagePrefix` 隔离；需要复用无前缀 key 时可显式传入 `storagePrefix: ''`。
- 包内不依赖 React、Jotai、Vite、Electron 或 `@wujie-shell/logger`。
- 包内类型只描述基础云端协议和轻量响应结构；产品 API 类型留在产品 UI，例如 `apps/desktop-cn/src/api/types.ts`。
- `./legacy-client` 子路径已移除；新代码只从包根入口 `@wujie-shell/business` 使用能力。

## 创建客户端

推荐在调用方适配层中创建客户端：

```ts
import { createWujieAiBusiness } from '@wujie-shell/business'

export const wujieBusiness = createWujieAiBusiness({
  env: 'prod',
  endpoint: 'https://api.example.com',
  storagePrefix: 'wujie:prod',
  storage: window.localStorage,
  fetch: window.fetch.bind(window),
})
```

当前 `apps/desktop-cn` 为了复用已有 `gate-token`、`user-id`、`device_id` key，使用无前缀存储：

```ts
const business = createWujieAiBusiness({
  env: 'wujieai',
  endpoint: getWujieAiServerUrl(),
  storage: businessStorage,
  storagePrefix: '',
  fetch: (url, init) => fetch(url, init),
})
```

`createWujieAiBusiness()` 返回：

```ts
{
  env: string
  endpoint: string
  auth: WujieAuthService
  apiKey: WujieApiKeyService
  models: WujieModelService
  skills: WujieSkillService
  billing: WujieBillingService
  templates: WujieTemplateService
}
```

## Service API 完整索引

以下是当前包根入口公开的 service 方法。参数和返回类型从 `@wujie-shell/business` 导入；实际
安装版本的 TypeScript 声明是最终事实来源。

### `auth`

| 方法 | 用途 |
| --- | --- |
| `sendCaptcha(input)` | 发送登录/注册验证码。 |
| `loginOrRegister(input)` | 使用验证码或密码登录/注册，并保存 session。 |
| `logout()` | 远端登出并清理本地 session。 |
| `getCurrentUser()` | 获取当前用户信息。 |
| `updateCurrentUser(input)` | 更新当前用户资料。 |
| `changePhone(input)` | 修改绑定手机号。 |
| `resetPassword(input)` | 重置密码。 |
| `getWechatAuthUrl(input)` | 获取微信授权 URL。 |
| `wechatLogin(input)` | 使用微信授权结果登录。 |
| `listSocialAccounts()` | 查询已绑定社交账号。 |
| `bindSocialAccount(input)` | 绑定社交账号。 |
| `unbindSocialAccount(input)` | 解绑社交账号。 |
| `getSessionSnapshot()` / `getSession()` | 读取当前本地 session。 |
| `clearSession()` | 只清理本地 session。 |

### `apiKey`

| 方法 | 用途 |
| --- | --- |
| `getBigModelUserInfo()` | 获取云端返回的完整大模型用户信息。 |
| `getBigModelConfig()` | 获取规范化 provider、key、模型和 base URL。 |
| `getBigModelApiKey()` | 只获取大模型 API Key。 |
| `getGatewayApiKey()` | 从当前用户信息获取 AI gateway key。 |

### `models`

| 方法 | 用途 |
| --- | --- |
| `listSupportedProviders()` | 获取支持的模型供应商。 |
| `listSelfOperatedModels()` | 获取自营模型列表。 |
| `getModelActivity(query)` | 查询模型活动分页数据。 |

### `skills`

| 方法 | 用途 |
| --- | --- |
| `listSkillMarket(payload)` | 查询 Skill 市场分页列表。 |
| `checkInstalledSkillUpdates(payload)` | 批量检查已安装 Skill 更新。 |
| `getSkillVersionStatus(payload)` | 查询单个 Skill 版本状态。 |

### `billing`

| 方法 | 用途 |
| --- | --- |
| `getMembershipInfo(query?)` | 获取会员信息。 |
| `getCreditBalance()` | 获取积分/余额。 |
| `createRechargeOrder(payload)` | 创建充值订单。 |
| `getPaymentStatus(payload)` | 查询支付状态。 |
| `listPayTypes()` | 获取支付方式。 |
| `listRechargeRecords(query?)` | 查询充值记录。 |
| `listMemberActivationRecords()` | 查询会员激活记录。 |

### `templates`

| 方法 | 用途 |
| --- | --- |
| `listMirrorTemplates(query?)` | 获取镜像模板列表。 |
| `listSupportAgents(query?)` | 获取支持的 Agent。 |
| `listSupportTemplates(query?)` | 获取支持的模板。 |
| `getCacheForView(query)` | 获取页面缓存业务数据。 |

不要根据方法名猜测 payload。先查看导出的 input/query 类型；返回 `unknown` 的接口必须在产品 API
适配层进行运行时校验和 UI 类型收敛。

## 登录/注册流程

发送验证码：

```ts
await wujieBusiness.auth.sendCaptcha({
  mobile: '13800000000',
  areaCode: '+86',
  behaviorParam: captchaPayload,
})
```

`behaviorParam` 不是可选占位对象。desktop-cn 当前先通过 vendored 云片行为验证 SDK
`src/third-party/yunpian.js` 初始化 `window.YpRiddler`，成功后取得 `token` 和 `authenticate`，再将
两个字段完整传给 `auth.sendCaptcha()`。business 包只透传并调用验证码 API，不负责加载云片 SDK。
迁移登录/注册或换绑手机号时必须同时迁移/等价实现该 UI 侧验证链，不能只迁移本段 business 调用。

`areaCode` 会在包内规范成不带 `+` 的格式，例如 `+86` 会发送为 `86`。

验证码登录或注册：

```ts
const login = await wujieBusiness.auth.loginOrRegister({
  mobile: '13800000000',
  areaCode: '+86',
  code: '123456',
})
```

密码登录或注册：

```ts
const login = await wujieBusiness.auth.loginOrRegister({
  mobile: '13800000000',
  areaCode: '+86',
  password: 'password',
})
```

密码登录时，调用方传入明文密码即可，业务包会发送 MD5 后的密码。如果产品 UI 已在表单层完成 MD5，则不要再调用 `auth.loginOrRegister()`，应在产品适配层自行调用对应路由，避免二次加密。

登录成功后，业务包会自动写入：

```text
${storagePrefix}:gate-token
${storagePrefix}:user-id
${storagePrefix}:device_id
```

当 `storagePrefix: ''` 时，写入 key 为：

```text
gate-token
user-id
device_id
```

后续请求会自动注入：

```text
Content-Type: application/json
gate-token: <token>
app_code: trade
source_site: AIPC
device_id: <device id>
```

登出：

```ts
await wujieBusiness.auth.logout()
```

无论远端登出接口是否成功返回，本地 session 都会被清理。

## API Key 获取

获取大模型配置：

```ts
const modelConfig = await wujieBusiness.apiKey.getBigModelConfig()
```

返回：

```ts
{
  provider: string
  apiKey: string
  modelName: string
  baseUrl: string
  raw: BigModelUserInfo
}
```

字段映射来自 `/wj-open/v2/claw/aipc/billing/big-model/user/info`：

```text
provider   <- provider
apiKey     <- key
modelName  <- model_name
baseUrl    <- host
```

只获取大模型 API Key：

```ts
const apiKey = await wujieBusiness.apiKey.getBigModelApiKey()
```

获取当前用户信息里的 AI gateway API Key：

```ts
const gatewayApiKey = await wujieBusiness.apiKey.getGatewayApiKey()
```

该方法会调用当前用户信息接口，并读取 `ai_gateway_api_key`。

## 路由常量

`WUJIE_AI_ROUTES` 保留无界云端 API 路径定义，供产品适配层复用。它只定义协议路径，不携带产品默认参数，也不决定请求 body/query/header。

```ts
import { WUJIE_AI_ROUTES } from '@wujie-shell/business'

await fetch(`${endpoint}${WUJIE_AI_ROUTES.payTypeList}`, {
  method: 'POST',
  body: JSON.stringify({ payScene, payDevice }),
})
```

当前路由覆盖：

```text
GET  /wj-open/v2/claw/template/templates
GET  /wj-open/v2/claw/aipc/mirror/templates
POST /wj-open/v2/claw/aipc/account/verifyCode
POST /wj-open/v2/claw/aipc/account/register
POST /wj-open/v2/claw/aipc/account/logout
GET  /wj-open/v2/claw/aipc/account/getUserInfo
POST /wj-open/v2/claw/aipc/account/changBind
GET  /wj-open/v2/claw/aipc/account/wechat/auth/goto_auth_url
POST /wj-open/v2/claw/aipc/account/social/wechat/login
POST /wj-open/v2/claw/aipc/account/social/authBind
POST /wj-open/v2/claw/aipc/account/social/authUnBind
POST /wj-open/v2/claw/aipc/account/updateUserInfo
POST /wj-open/v2/claw/aipc/account/resetPassword
GET  /wj-open/v2/claw/aipc/account/bindSocialAccounts
GET  /wujieai/redline/getCacheForView
POST /wj-open/v2/claw/aipc/billing/order/rechargePay
GET  /wj-open/v2/claw/aipc/billing/order/getRechargeResult
POST /wj-open/v2/claw/aipc/billing/order/payTypeList
GET  /wj-open/v2/claw/aipc/billing/order/getClawRechargeRecords
GET  /wj-open/v2/claw/aipc/billing/credit/balance
GET  /wj-open/v2/claw/aipc/billing/member/info
GET  /wj-open/v2/claw/aipc/billing/big-model/user/info
GET  /wj-open/v2/claw/aipc/models/self-operated
GET  /wj-open/v2/claw/aipc/models/support-providers
GET  /wj-open/v2/claw/aipc/billing/big-model/user/activity/page
GET  /wj-open/v2/claw/aipc/billing/member/activation/records
POST /wj-open/v2/claw/aipc/extension-versions/skill/page
POST /wj-open/v2/claw/aipc/extension-versions/only
POST /wj-open/v2/claw/aipc/extension-versions/has-update
```

Skill 市场、支付、充值和模板接口中已经稳定下来的无界 AIPC 默认参数由 business service 统一封装；页面级类型转换、展示模型、模板选择状态和调用组合仍由 `apps/desktop-cn/src/api/*Client.ts` 或具体调用方决定。

## 日志

业务包只提供可选 `logger` hook：

```ts
createWujieAiBusiness({
  // ...
  logger(event) {
    console.info(event.level, event.message, event.meta)
  },
})
```

`@wujie-shell/business` 不直接依赖 `@wujie-shell/logger`，避免把 Node-only 文件日志能力带入 renderer 或其他宿主。Shell 主进程如需落盘日志，应在宿主侧创建 `@wujie-shell/logger`，再适配为 business 的 `logger` 回调。

## 不在当前包内处理

- UI 环境变量读取。
- React/Jotai 状态。
- 路由守卫。
- Electron IPC。
- `window.wujieShell` 或 `@wujie-shell/sdk` 调用。
- OpenClaw 模型配置写入。
- 页面请求组合、分页展示转换、模板选择状态、附件类型映射和组件交互状态。
- 产品 UI 类型收敛和页面级适配；这些应在调用方适配层实现。

如果 UI 拿到大模型配置后需要写入 OpenClaw，应在 UI 或 Shell 适配层完成：

```ts
const config = await wujieBusiness.apiKey.getBigModelConfig()

await applyProductModelSettings({
  provider: config.provider,
  apiKey: config.apiKey,
  modelName: config.modelName,
  modelBaseUrl: config.baseUrl,
})
```

## 验证

```bash
cd apps/shell
pnpm --filter @wujie-shell/business test
pnpm --filter @wujie-shell/business build

cd ../desktop-cn
pnpm typecheck
```
