# 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
}
```

## 登录/注册流程

发送验证码：

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

`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
```
