@h-ai/serv 0.1.0-alpha.35 → 0.1.0-alpha.37

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 CHANGED
@@ -10,7 +10,7 @@
10
10
  - 默认 feature procedures:`createIamProcedures()`、`createStorageProcedures()`、`createAiProcedures()`
11
11
  - 内置安全响应头、健康检查、可选 OpenAPI JSON、可选 Scalar 文档页、可选内部 RPC endpoint
12
12
  - 可选传输加密:`serv.createApp({ transport: { crypto } })` 自动挂载密钥协商与请求/响应加解密
13
- - 可选语音入口:`serv.createApp({ audio: { ai } })` 在 `${apiPrefix}/ai/audio` 挂载统一语音 WebSocket,基于 `ai.audio` 提供非实时/实时的语音识别与合成(复用 `iam` 令牌校验鉴权,`serv.listen` 自动完成 WS 升级注入)
13
+ - 可选语音入口:`serv.createApp({ audio: { ai, verifyTicket, authorize } })` 挂载统一语音 WebSocket;连接建立即用一次性 ticket 鉴权(返回 `{ session, grant? }`,独立预鉴权超时),每帧运行时 Zod 校验 + 严格状态机,输入/积压/发送缓冲背压与级联取消;授权回调负责服务端确认操作、模型、音色、配额与并发占用
14
14
 
15
15
  > 生命周期说明:`@h-ai/serv` 本身是无状态 HTTP App 装配器,不需要 `init()` / `close()`;请在创建 app 前初始化 `iam` / `storage` / `ai` / `crypto` 等依赖,并在 `serv.listen(..., { onClose })` 中反向释放它们。`serv.createApp()` 遇到启动期配置错误(例如 transport manager 创建失败)会 fail-fast 抛出,便于进程启动阶段暴露问题。
16
16
 
@@ -68,6 +68,32 @@ const handler = serv.toFetch(app)
68
68
  export default { fetch: handler }
69
69
  ```
70
70
 
71
+ ### Audio WebSocket:ticket 与服务端授权
72
+
73
+ 语音入口必须使用短期、一次性 ticket。应用通过已登录 HTTP endpoint 签发 ticket(推荐 `iam.ticket.issue`),并在 `verifyTicket` 中原子消费(推荐 `iam.ticket.consume`);连接建立即完成鉴权(独立预鉴权超时),未鉴权不处理任何业务帧。框架保存返回的 `ServSession` 与可选 `grant`,再调用 `authorize` 确认付费参数:
74
+
75
+ ```ts
76
+ const app = serv.createApp({
77
+ contract,
78
+ procedures,
79
+ iam,
80
+ audio: {
81
+ ai,
82
+ verifyTicket: consumeAudioTicket, // → HaiResult<{ session, grant? }>
83
+ authorize: async (session, request, grant) => authorizeAudioRequest(session, request, grant),
84
+ onSessionEnd: async (session, request) => releaseAudioConcurrency(session.userId, request.operation),
85
+ },
86
+ })
87
+ ```
88
+
89
+ - `verifyTicket(ticket)` 必须校验用途、过期时间并保证同一 ticket 只能成功一次,返回 `HaiResult<AudioTicketVerification>`(`{ session, grant? }`);`grant` 承载票据绑定的操作/模型/会话。
90
+ - `authorize(session, request, grant)` 适合检查 IAM 权限、模型/音色白名单、Persona、调用配额并占用并发名额,并用 `grant` 交叉校验 `start` 未越权;返回的 `AuthorizedAudioRequest` 才会传给 `ai.audio`。
91
+ - 未提供 `authorize` 时,框架会忽略客户端的 `model`、`voice` 与 `instruction`,只使用服务端默认模型(若票据 `grant.model` 存在则采用之),并拒绝与 `grant.operation` 不符的 `start`。
92
+ - 协议边界对每一帧做运行时 Zod 校验并按严格状态机接受(识别只收音频、合成只收文本、`start`/`done` 各一次、`segmentId` 会话内唯一);输入队列、消息积压与发送缓冲均有上限,超限或取消时以领域错误关闭并级联中止上游。
93
+ - 可调参数:`preAuthTimeoutMs`(默认 5000)、`maxPendingMessages`(默认 256)、`maxSendBufferBytes`(默认 8 MiB)、`maxMessageBytes` / `maxBufferedBytes` / `maxTextBytes` / `maxSessionMs`。
94
+ - `onSessionEnd` 在成功、失败或断连后至多调用一次,供应用释放并发占用。
95
+ - 浏览器 `createAIClient({ audio: { url, getTicket } })` 只把一次性 `ticket` 放入 WebSocket URL;`getTicket(request)` 接收本次操作摘要(`operation` / `model`),可签发与本次操作严格绑定的票据;禁止传普通 IAM access token。
96
+
71
97
  ### 传输加密(与 `@h-ai/crypto` 统一)
72
98
 
73
99
  serv 不暴露本地传输加密工厂;统一从 `crypto.transport` 创建服务端管理器。使用方只需传入顶层 `crypto` 句柄:
@@ -239,11 +239,11 @@ declare function createAiProcedures(deps: AiProcedureDeps): {
239
239
  query: zod.ZodString;
240
240
  topK: zod.ZodOptional<zod.ZodNumber>;
241
241
  types: zod.ZodOptional<zod.ZodArray<zod.ZodEnum<{
242
+ instruction: "instruction";
242
243
  fact: "fact";
243
244
  preference: "preference";
244
245
  event: "event";
245
246
  entity: "entity";
246
- instruction: "instruction";
247
247
  }>>>;
248
248
  minImportance: zod.ZodOptional<zod.ZodNumber>;
249
249
  objectId: zod.ZodOptional<zod.ZodString>;
@@ -254,11 +254,11 @@ declare function createAiProcedures(deps: AiProcedureDeps): {
254
254
  id: zod.ZodString;
255
255
  content: zod.ZodString;
256
256
  type: zod.ZodEnum<{
257
+ instruction: "instruction";
257
258
  fact: "fact";
258
259
  preference: "preference";
259
260
  event: "event";
260
261
  entity: "entity";
261
- instruction: "instruction";
262
262
  }>;
263
263
  importance: zod.ZodNumber;
264
264
  objectId: zod.ZodOptional<zod.ZodString>;
@@ -283,11 +283,11 @@ declare function createAiProcedures(deps: AiProcedureDeps): {
283
283
  }, zod_v4_core.$strip>], "success">, _orpc_contract.MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
284
284
  list: _orpc_server.Procedure<_orpc_server.MergedInitialContext<ServContext & Record<never, never>, ServContext & Record<never, never>, ServContext>, ServContext, zod.ZodObject<{
285
285
  types: zod.ZodOptional<zod.ZodArray<zod.ZodEnum<{
286
+ instruction: "instruction";
286
287
  fact: "fact";
287
288
  preference: "preference";
288
289
  event: "event";
289
290
  entity: "entity";
290
- instruction: "instruction";
291
291
  }>>>;
292
292
  objectId: zod.ZodOptional<zod.ZodString>;
293
293
  offset: zod.ZodOptional<zod.ZodNumber>;
@@ -299,11 +299,11 @@ declare function createAiProcedures(deps: AiProcedureDeps): {
299
299
  id: zod.ZodString;
300
300
  content: zod.ZodString;
301
301
  type: zod.ZodEnum<{
302
+ instruction: "instruction";
302
303
  fact: "fact";
303
304
  preference: "preference";
304
305
  event: "event";
305
306
  entity: "entity";
306
- instruction: "instruction";
307
307
  }>;
308
308
  importance: zod.ZodNumber;
309
309
  objectId: zod.ZodOptional<zod.ZodString>;
@@ -144,6 +144,8 @@ declare function createIamProcedures(deps: IamProcedureDeps): {
144
144
  createdAt: zod.ZodCoercedDate<unknown>;
145
145
  updatedAt: zod.ZodCoercedDate<unknown>;
146
146
  metadata: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodUnknown>>;
147
+ roles: zod.ZodArray<zod.ZodString>;
148
+ permissions: zod.ZodArray<zod.ZodString>;
147
149
  }, zod_v4_core.$strip>;
148
150
  }, zod_v4_core.$strip>, zod.ZodObject<{
149
151
  success: zod.ZodLiteral<false>;
@@ -20,11 +20,15 @@ function createIamProcedures(deps) {
20
20
  return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM("serv_errorUnauthorized", { locale: context.locale })));
21
21
  return iam.auth.logout(token);
22
22
  })),
23
- currentUser: p.auth.currentUser.handler(requireAuth(({ context }) => {
23
+ currentUser: p.auth.currentUser.handler(requireAuth(async ({ context }) => {
24
24
  const token = context.accessToken;
25
25
  if (!token)
26
26
  return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM("serv_errorUnauthorized", { locale: context.locale })));
27
- return iam.user.getCurrentUser(token);
27
+ return mapHaiResult(await iam.user.getCurrentUser(token), (user) => ({
28
+ ...user,
29
+ roles: context.session?.roles ?? [],
30
+ permissions: context.session?.permissions ?? []
31
+ }));
28
32
  })),
29
33
  refresh: p.auth.refresh.handler(async ({ input }) => {
30
34
  return mapHaiResult(await iam.session.refresh(input.refreshToken), (tokens) => ({ tokens }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/features/serv-feature-iam.ts"],"names":[],"mappings":"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","file":"iam.js","sourcesContent":["/**\n * @h-ai/serv — IAM 默认 procedures\n *\n * 基于 `@h-ai/iam` 提供开箱即用的 IAM procedures 实现:认证、用户管理、角色权限 CRUD。\n * 通过 `createIamProcedures(deps)` 组装后直接挂载到 oRPC router。\n * @module features/serv-feature-iam\n */\n\nimport type {\n IamAdminCreateUserInput,\n IamAdminResetPasswordInput,\n IamAdminUpdateUserInput,\n IamChangePasswordInput,\n IamCreatePermissionInput,\n IamCreateRoleInput,\n IamListPermissionsInput,\n IamListUsersInput,\n IamLoginInput,\n IamLogoutInput,\n IamOtpLoginInput,\n IamRegisterInput,\n IamSendOtpInput,\n IamUpdateCurrentUserInput,\n IamUpdateRoleInput,\n IamUserIdInput,\n} from '@h-ai/api-contract'\nimport type { HaiResult, PaginatedResult } from '@h-ai/core'\nimport type { IamFunctions, Permission, Role, TokenPair, User } from '@h-ai/iam'\nimport type { ServContext } from '../serv-context.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { implement } from '@orpc/server'\nimport { requireAuth } from '../pipelines/serv-pipeline-require-auth.js'\nimport { requirePermission } from '../pipelines/serv-pipeline-require-permission.js'\nimport { servM } from '../serv-i18n.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst iamContract = apiContract.iam\n\nconst iamFeatureLogger = core.logger.child({ module: 'serv', scope: 'feature-iam' })\n\n/** IAM 默认 procedures 依赖。 */\nexport interface IamProcedureDeps {\n readonly iam: IamFunctions\n}\n\n/** 创建 IAM 默认 procedures。 */\nexport function createIamProcedures(deps: IamProcedureDeps) {\n const p = implement(iamContract).$context<ServContext>()\n const { iam } = deps\n\n return p.router({\n auth: {\n login: p.auth.login.handler(({ input }: { input: IamLoginInput }) => iam.auth.login(input)),\n loginWithOtp: p.auth.loginWithOtp.handler(({ input }: { input: IamOtpLoginInput }) => iam.auth.loginWithOtp(input)),\n logout: p.auth.logout.handler(requireAuth<IamLogoutInput, void>(({ input, context }) => {\n // 不再使用空字符串兜底:requireAuth 已确保 session 存在,但 accessToken 字段可能仍缺失\n // (例如自定义 createContext 未填充);此时显式返回 UNAUTHORIZED 而非把空 token 透传给 IAM。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale })))\n return iam.auth.logout(token)\n })),\n currentUser: p.auth.currentUser.handler(requireAuth<unknown, User>(({ context }) => {\n const token = context.accessToken\n if (!token)\n return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale })))\n return iam.user.getCurrentUser(token)\n })),\n refresh: p.auth.refresh.handler(async ({ input }: { input: { refreshToken: string } }) => {\n return mapHaiResult(await iam.session.refresh(input.refreshToken), tokens => ({ tokens }))\n }),\n sendOtp: p.auth.sendOtp.handler(({ input }: { input: IamSendOtpInput }) => iam.auth.sendOtp(input.identifier)),\n register: p.auth.register.handler(({ input }: { input: IamRegisterInput }) => iam.auth.registerAndLogin(input)),\n changePassword: p.auth.changePassword.handler(requireAuth<IamChangePasswordInput, void>(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale })))\n return iam.user.changeCurrentUserPassword(token, input.oldPassword, input.newPassword)\n })),\n updateCurrentUser: p.auth.updateCurrentUser.handler(requireAuth<IamUpdateCurrentUserInput, User>(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale })))\n return iam.user.updateCurrentUser(token, input)\n })),\n },\n users: {\n list: p.users.list.handler(requirePermission<IamListUsersInput, PaginatedResult<User>>('iam.users.read', ({ input }) => {\n return iam.user.listUsers(input)\n })),\n get: p.users.get.handler(requirePermission<IamUserIdInput, User | null>('iam.users.read', ({ input }) => {\n return iam.user.getUser(input.id, { include: ['roles'] })\n })),\n create: p.users.create.handler(requirePermission<IamAdminCreateUserInput, User>('iam.users.write', ({ input }) => {\n return createUser(iam, input)\n })),\n update: p.users.update.handler(requirePermission<IamAdminUpdateUserInput, User>('iam.users.write', ({ input }) => {\n return updateUser(iam, input)\n })),\n delete: p.users.delete.handler(requirePermission<IamUserIdInput, void>('iam.users.write', ({ input, context }) => {\n // 防止自删:避免管理员误删自己的账号导致授权锁死。\n if (context.session?.userId === input.id)\n return Promise.resolve(err(HaiCommonError.FORBIDDEN, servM('serv_iamCannotDeleteCurrentUser', { locale: context.locale })))\n return iam.user.deleteUser(input.id)\n })),\n resetPassword: p.users.resetPassword.handler(requirePermission<IamAdminResetPasswordInput, void>('iam.users.write', ({ input }) => {\n return iam.user.adminResetPassword(input.id, input.newPassword)\n })),\n },\n roles: {\n list: p.roles.list.handler(requirePermission<unknown, PaginatedResult<Role>>('iam.roles.read', () => {\n return iam.authz.getAllRoles()\n })),\n get: p.roles.get.handler(requirePermission<IamUserIdInput, Role | null>('iam.roles.read', ({ input }) => {\n return iam.authz.getRole(input.id)\n })),\n create: p.roles.create.handler(requirePermission<IamCreateRoleInput, Role>('iam.roles.write', ({ input }) => {\n return iam.authz.createRole(input)\n })),\n update: p.roles.update.handler(requirePermission<IamUpdateRoleInput, Role>('iam.roles.write', ({ input }) => {\n const { id, ...data } = input\n return iam.authz.updateRole(id, data)\n })),\n delete: p.roles.delete.handler(requirePermission<IamUserIdInput, void>('iam.roles.write', ({ input }) => {\n return iam.authz.deleteRole(input.id)\n })),\n },\n permissions: {\n list: p.permissions.list.handler(requirePermission<IamListPermissionsInput, PaginatedResult<Permission>>('iam.permissions.read', ({ input }) => {\n return iam.authz.getAllPermissions(input)\n })),\n get: p.permissions.get.handler(requirePermission<IamUserIdInput, Permission | null>('iam.permissions.read', ({ input }) => {\n return iam.authz.getPermission(input.id)\n })),\n create: p.permissions.create.handler(requirePermission<IamCreatePermissionInput, Permission>('iam.permissions.write', ({ input }) => {\n return iam.authz.createPermission(input)\n })),\n delete: p.permissions.delete.handler(requirePermission<IamUserIdInput, void>('iam.permissions.write', ({ input }) => {\n return iam.authz.deletePermission(input.id)\n })),\n },\n })\n}\n\nasync function createUser(iam: IamFunctions, input: IamAdminCreateUserInput): Promise<HaiResult<User>> {\n const registerResult = await iam.user.register(input)\n if (!registerResult.success)\n return registerResult\n\n const userId = registerResult.data.user.id\n\n // 任何后续步骤失败都需要回滚刚创建的用户,以避免重复用户名/邮箱错误阅存。\n async function rollback(reason: string): Promise<void> {\n const deleteResult = await iam.user.deleteUser(userId)\n if (!deleteResult.success)\n iamFeatureLogger.error('createUser rollback failed, orphan user may exist', { userId, reason, error: deleteResult.error })\n }\n\n if (input.roleIds?.length) {\n const rolesResult = await iam.authz.syncRoles(userId, input.roleIds)\n if (!rolesResult.success) {\n await rollback('syncRoles failed')\n return rolesResult\n }\n }\n\n if (input.enabled === false) {\n const updateResult = await iam.user.updateUser(userId, { enabled: false })\n if (!updateResult.success) {\n await rollback('initial disable failed')\n return updateResult\n }\n return updateResult\n }\n\n return ok(registerResult.data.user)\n}\n\nasync function updateUser(iam: IamFunctions, input: IamAdminUpdateUserInput): Promise<HaiResult<User>> {\n const { id, roleIds, ...data } = input\n const updateResult = await iam.user.updateUser(id, data)\n if (!updateResult.success)\n return updateResult\n\n if (roleIds) {\n const rolesResult = await iam.authz.syncRoles(id, roleIds)\n if (!rolesResult.success)\n return rolesResult\n }\n\n return updateResult\n}\n\n// re-export 类型以便 IDE 跳转\nexport type { TokenPair }\n"]}
1
+ {"version":3,"sources":["../../src/features/serv-feature-iam.ts"],"names":[],"mappings":"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","file":"iam.js","sourcesContent":["/**\n * @h-ai/serv — IAM 默认 procedures\n *\n * 基于 `@h-ai/iam` 提供开箱即用的 IAM procedures 实现:认证、用户管理、角色权限 CRUD。\n * 通过 `createIamProcedures(deps)` 组装后直接挂载到 oRPC router。\n * @module features/serv-feature-iam\n */\n\nimport type {\n IamAdminCreateUserInput,\n IamAdminResetPasswordInput,\n IamAdminUpdateUserInput,\n IamChangePasswordInput,\n IamCreatePermissionInput,\n IamCreateRoleInput,\n IamCurrentUser,\n IamListPermissionsInput,\n IamListUsersInput,\n IamLoginInput,\n IamLogoutInput,\n IamOtpLoginInput,\n IamRegisterInput,\n IamSendOtpInput,\n IamUpdateCurrentUserInput,\n IamUpdateRoleInput,\n IamUserIdInput,\n} from '@h-ai/api-contract'\nimport type { HaiResult, PaginatedResult } from '@h-ai/core'\nimport type { IamFunctions, Permission, Role, TokenPair, User } from '@h-ai/iam'\nimport type { ServContext } from '../serv-context.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { implement } from '@orpc/server'\nimport { requireAuth } from '../pipelines/serv-pipeline-require-auth.js'\nimport { requirePermission } from '../pipelines/serv-pipeline-require-permission.js'\nimport { servM } from '../serv-i18n.js'\nimport { mapHaiResult } from './serv-feature-helpers.js'\n\nconst iamContract = apiContract.iam\n\nconst iamFeatureLogger = core.logger.child({ module: 'serv', scope: 'feature-iam' })\n\n/** IAM 默认 procedures 依赖。 */\nexport interface IamProcedureDeps {\n readonly iam: IamFunctions\n}\n\n/** 创建 IAM 默认 procedures。 */\nexport function createIamProcedures(deps: IamProcedureDeps) {\n const p = implement(iamContract).$context<ServContext>()\n const { iam } = deps\n\n return p.router({\n auth: {\n login: p.auth.login.handler(({ input }: { input: IamLoginInput }) => iam.auth.login(input)),\n loginWithOtp: p.auth.loginWithOtp.handler(({ input }: { input: IamOtpLoginInput }) => iam.auth.loginWithOtp(input)),\n logout: p.auth.logout.handler(requireAuth<IamLogoutInput, void>(({ input, context }) => {\n // 不再使用空字符串兜底:requireAuth 已确保 session 存在,但 accessToken 字段可能仍缺失\n // (例如自定义 createContext 未填充);此时显式返回 UNAUTHORIZED 而非把空 token 透传给 IAM。\n const token = input.accessToken ?? context.accessToken\n if (!token)\n return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale })))\n return iam.auth.logout(token)\n })),\n currentUser: p.auth.currentUser.handler(requireAuth<unknown, IamCurrentUser>(async ({ context }) => {\n const token = context.accessToken\n if (!token)\n return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale })))\n return mapHaiResult<User, IamCurrentUser>(await iam.user.getCurrentUser(token), user => ({\n ...user,\n roles: context.session?.roles ?? [],\n permissions: context.session?.permissions ?? [],\n }))\n })),\n refresh: p.auth.refresh.handler(async ({ input }: { input: { refreshToken: string } }) => {\n return mapHaiResult(await iam.session.refresh(input.refreshToken), tokens => ({ tokens }))\n }),\n sendOtp: p.auth.sendOtp.handler(({ input }: { input: IamSendOtpInput }) => iam.auth.sendOtp(input.identifier)),\n register: p.auth.register.handler(({ input }: { input: IamRegisterInput }) => iam.auth.registerAndLogin(input)),\n changePassword: p.auth.changePassword.handler(requireAuth<IamChangePasswordInput, void>(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale })))\n return iam.user.changeCurrentUserPassword(token, input.oldPassword, input.newPassword)\n })),\n updateCurrentUser: p.auth.updateCurrentUser.handler(requireAuth<IamUpdateCurrentUserInput, User>(({ input, context }) => {\n const token = context.accessToken\n if (!token)\n return Promise.resolve(err(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: context.locale })))\n return iam.user.updateCurrentUser(token, input)\n })),\n },\n users: {\n list: p.users.list.handler(requirePermission<IamListUsersInput, PaginatedResult<User>>('iam.users.read', ({ input }) => {\n return iam.user.listUsers(input)\n })),\n get: p.users.get.handler(requirePermission<IamUserIdInput, User | null>('iam.users.read', ({ input }) => {\n return iam.user.getUser(input.id, { include: ['roles'] })\n })),\n create: p.users.create.handler(requirePermission<IamAdminCreateUserInput, User>('iam.users.write', ({ input }) => {\n return createUser(iam, input)\n })),\n update: p.users.update.handler(requirePermission<IamAdminUpdateUserInput, User>('iam.users.write', ({ input }) => {\n return updateUser(iam, input)\n })),\n delete: p.users.delete.handler(requirePermission<IamUserIdInput, void>('iam.users.write', ({ input, context }) => {\n // 防止自删:避免管理员误删自己的账号导致授权锁死。\n if (context.session?.userId === input.id)\n return Promise.resolve(err(HaiCommonError.FORBIDDEN, servM('serv_iamCannotDeleteCurrentUser', { locale: context.locale })))\n return iam.user.deleteUser(input.id)\n })),\n resetPassword: p.users.resetPassword.handler(requirePermission<IamAdminResetPasswordInput, void>('iam.users.write', ({ input }) => {\n return iam.user.adminResetPassword(input.id, input.newPassword)\n })),\n },\n roles: {\n list: p.roles.list.handler(requirePermission<unknown, PaginatedResult<Role>>('iam.roles.read', () => {\n return iam.authz.getAllRoles()\n })),\n get: p.roles.get.handler(requirePermission<IamUserIdInput, Role | null>('iam.roles.read', ({ input }) => {\n return iam.authz.getRole(input.id)\n })),\n create: p.roles.create.handler(requirePermission<IamCreateRoleInput, Role>('iam.roles.write', ({ input }) => {\n return iam.authz.createRole(input)\n })),\n update: p.roles.update.handler(requirePermission<IamUpdateRoleInput, Role>('iam.roles.write', ({ input }) => {\n const { id, ...data } = input\n return iam.authz.updateRole(id, data)\n })),\n delete: p.roles.delete.handler(requirePermission<IamUserIdInput, void>('iam.roles.write', ({ input }) => {\n return iam.authz.deleteRole(input.id)\n })),\n },\n permissions: {\n list: p.permissions.list.handler(requirePermission<IamListPermissionsInput, PaginatedResult<Permission>>('iam.permissions.read', ({ input }) => {\n return iam.authz.getAllPermissions(input)\n })),\n get: p.permissions.get.handler(requirePermission<IamUserIdInput, Permission | null>('iam.permissions.read', ({ input }) => {\n return iam.authz.getPermission(input.id)\n })),\n create: p.permissions.create.handler(requirePermission<IamCreatePermissionInput, Permission>('iam.permissions.write', ({ input }) => {\n return iam.authz.createPermission(input)\n })),\n delete: p.permissions.delete.handler(requirePermission<IamUserIdInput, void>('iam.permissions.write', ({ input }) => {\n return iam.authz.deletePermission(input.id)\n })),\n },\n })\n}\n\nasync function createUser(iam: IamFunctions, input: IamAdminCreateUserInput): Promise<HaiResult<User>> {\n const registerResult = await iam.user.register(input)\n if (!registerResult.success)\n return registerResult\n\n const userId = registerResult.data.user.id\n\n // 任何后续步骤失败都需要回滚刚创建的用户,以避免重复用户名/邮箱错误阅存。\n async function rollback(reason: string): Promise<void> {\n const deleteResult = await iam.user.deleteUser(userId)\n if (!deleteResult.success)\n iamFeatureLogger.error('createUser rollback failed, orphan user may exist', { userId, reason, error: deleteResult.error })\n }\n\n if (input.roleIds?.length) {\n const rolesResult = await iam.authz.syncRoles(userId, input.roleIds)\n if (!rolesResult.success) {\n await rollback('syncRoles failed')\n return rolesResult\n }\n }\n\n if (input.enabled === false) {\n const updateResult = await iam.user.updateUser(userId, { enabled: false })\n if (!updateResult.success) {\n await rollback('initial disable failed')\n return updateResult\n }\n return updateResult\n }\n\n return ok(registerResult.data.user)\n}\n\nasync function updateUser(iam: IamFunctions, input: IamAdminUpdateUserInput): Promise<HaiResult<User>> {\n const { id, roleIds, ...data } = input\n const updateResult = await iam.user.updateUser(id, data)\n if (!updateResult.success)\n return updateResult\n\n if (roleIds) {\n const rolesResult = await iam.authz.syncRoles(id, roleIds)\n if (!rolesResult.success)\n return rolesResult\n }\n\n return updateResult\n}\n\n// re-export 类型以便 IDE 跳转\nexport type { TokenPair }\n"]}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as _h_ai_core from '@h-ai/core';
2
2
  import { HaiResult, ValidationFormError } from '@h-ai/core';
3
3
  import { Router, implement } from '@orpc/server';
4
+ import { AIFunctions, AudioWsStartMessage, AudioFormat } from '@h-ai/ai';
4
5
  import { AnyContractRouter, OpenAPI } from '@orpc/contract';
5
- import { AIFunctions } from '@h-ai/ai';
6
6
  import { S as ServSession, a as ServContext, b as ServIam, R as RefreshCookieConfig, C as CreateServContext, p as parseRequestContext, c as buildAuthContextFactory } from './serv-context-CatIx8r-.js';
7
7
  export { d as CreateServContextInput, e as RefreshTokenPair } from './serv-context-CatIx8r-.js';
8
8
  import { MiddlewareHandler } from 'hono';
@@ -17,6 +17,11 @@ import { AddressInfo } from 'node:net';
17
17
  * 基于 `@h-ai/ai` 的 `ai.audio` 提供开箱即用的语音 WebSocket 端点,同时支持非实时 / 实时
18
18
  * 的语音识别与语音合成。传输细节(WebSocket 帧、二进制/JSON 编解码)在此内部完成,
19
19
  * 客户端只使用统一的领域协议,不接触任何厂商原生事件。
20
+ *
21
+ * 安全与健壮性:
22
+ * - 连接建立后立即用一次性 ticket 完成鉴权(独立预鉴权超时),未鉴权不处理任何业务帧;
23
+ * - 每一帧经运行时 Zod 校验,按严格状态机接受(识别只收音频、合成只收文本、start/done 各一次);
24
+ * - 输入队列、消息积压、发送缓冲均有上限,超限即以领域错误关闭,取消时级联中止。
20
25
  * @module features/serv-feature-audio
21
26
  */
22
27
 
@@ -24,14 +29,26 @@ import { AddressInfo } from 'node:net';
24
29
  interface AudioWsDeps {
25
30
  /** AI 服务对象的语音能力(`ai.audio`)。 */
26
31
  readonly ai: Pick<AIFunctions, 'audio'>;
27
- /** 访问令牌校验(提供后连接必须携带有效令牌;未提供则不做鉴权)。 */
28
- readonly verifyToken?: (token: string) => Promise<HaiResult<ServSession>>;
32
+ /** 校验并原子消费短期、一次性的 Audio ticket,返回会话与可选授权。 */
33
+ readonly verifyTicket: (ticket: string) => Promise<HaiResult<AudioTicketVerification>>;
34
+ /** 基于会话授权操作、模型、音色、格式和配额;第三参为票据绑定授权(如有)。 */
35
+ readonly authorize?: (session: ServSession, request: AudioWsStartMessage, grant?: AudioTicketGrant) => Promise<HaiResult<AuthorizedAudioRequest>>;
36
+ /** 会话结束时释放应用侧并发占用。 */
37
+ readonly onSessionEnd?: (session: ServSession, request: AuthorizedAudioRequest) => void | Promise<void>;
29
38
  /** 单条消息字节上限(默认 1 MiB,防止内存放大)。 */
30
39
  readonly maxMessageBytes?: number;
31
40
  /** 单连接累计接收音频字节上限(默认 10 MiB,防止非流式缓冲无限增长)。 */
32
41
  readonly maxBufferedBytes?: number;
42
+ /** 单连接累计接收文本字节上限(默认 1 MiB,防止 TTS 文本输入无限增长与上游费用失控)。 */
43
+ readonly maxTextBytes?: number;
33
44
  /** 单连接最长持续时间(毫秒,默认 5 分钟,防止恶意长连接)。 */
34
45
  readonly maxSessionMs?: number;
46
+ /** 预鉴权超时(毫秒,默认 5000):连接建立后未完成鉴权并收到 start 即关闭。 */
47
+ readonly preAuthTimeoutMs?: number;
48
+ /** 未处理消息积压上限(默认 256):串行处理链积压超过此数立即关闭。 */
49
+ readonly maxPendingMessages?: number;
50
+ /** 发送缓冲高水位(字节,默认 8 MiB):bufferedAmount 超过即关闭慢客户端。 */
51
+ readonly maxSendBufferBytes?: number;
35
52
  }
36
53
 
37
54
  /**
@@ -339,13 +356,13 @@ interface CreateServAppOptions<TContract extends AnyContractRouter, TProcedures
339
356
  * 启用统一语音 WebSocket 入口(opt-in)。提供后 serv 自动:
340
357
  * 1. 在 `${apiPrefix}/ai/audio`(可自定义 `path`)注册语音 WebSocket 路由
341
358
  * 2. 基于 `ai.audio` 提供非实时 / 实时的语音识别与合成
342
- * 3. 复用同一套令牌校验(iam / verifyToken)对 WebSocket 连接鉴权
359
+ * 3. 通过一次性 Audio ticket 验证身份,再由授权回调确认付费参数
343
360
  *
344
361
  * 需配合 `serv.listen()`(Node 适配器会自动完成 WebSocket 升级注入)。
345
362
  *
346
363
  * @example
347
364
  * ```ts
348
- * serv.createApp({ contract, procedures, iam, audio: { ai } })
365
+ * serv.createApp({ contract, procedures, iam, audio: { ai, verifyTicket, authorize } })
349
366
  * ```
350
367
  */
351
368
  readonly audio?: ServAudioConfig;
@@ -354,14 +371,68 @@ interface CreateServAppOptions<TContract extends AnyContractRouter, TProcedures
354
371
  interface ServAudioConfig {
355
372
  /** AI 服务对象(提供 `ai.audio` 领域能力)。 */
356
373
  readonly ai: AudioWsDeps['ai'];
374
+ /**
375
+ * 校验并原子消费短期、一次性的 Audio ticket,返回会话与可选的能力授权(grant)。
376
+ *
377
+ * ticket 的签发和存储由应用决定(推荐 `iam.ticket.issue` / `iam.ticket.consume`);
378
+ * 成功结果必须返回建立授权所需的 IAM 会话摘要,可选返回签发时绑定的 `grant`
379
+ * (操作 / 模型 / 会话),供服务端交叉校验 `start` 帧与票据一致。
380
+ */
381
+ readonly verifyTicket: (ticket: string) => Promise<HaiResult<AudioTicketVerification>>;
382
+ /**
383
+ * 基于已验证会话授权本次操作,并返回服务端确认后的付费参数。
384
+ *
385
+ * 未提供时只保留操作及音频格式,客户端提交的 model / voice / instruction 不会生效。
386
+ * 这里适合完成 IAM 权限、Persona、套餐配额和并发会话的检查/占用。
387
+ * 第三参 `grant` 为票据绑定的能力(如有),可用于确认 `start` 未越权。
388
+ */
389
+ readonly authorize?: (session: ServSession, request: AudioWsStartMessage, grant?: AudioTicketGrant) => Promise<HaiResult<AuthorizedAudioRequest>>;
390
+ /** 会话结束钩子,供应用释放并发占用;无论成功、失败或断连都至多调用一次。 */
391
+ readonly onSessionEnd?: (session: ServSession, request: AuthorizedAudioRequest) => void | Promise<void>;
357
392
  /** 语音入口路径(相对 apiPrefix,默认 `/ai/audio`)。 */
358
393
  readonly path?: string;
359
394
  /** 单条消息字节上限(默认 1 MiB)。 */
360
395
  readonly maxMessageBytes?: number;
361
396
  /** 单连接累计接收音频字节上限(默认 10 MiB)。 */
362
397
  readonly maxBufferedBytes?: number;
398
+ /** 单连接累计接收文本字节上限(默认 1 MiB)。 */
399
+ readonly maxTextBytes?: number;
363
400
  /** 单连接最长持续时间(毫秒,默认 5 分钟)。 */
364
401
  readonly maxSessionMs?: number;
402
+ /** 预鉴权超时(毫秒,默认 5000):连接建立后未完成鉴权并收到 `start` 即关闭,独立于会话超时。 */
403
+ readonly preAuthTimeoutMs?: number;
404
+ /** 未处理消息积压上限(默认 256):串行处理链积压超过此数立即关闭,防止内存放大。 */
405
+ readonly maxPendingMessages?: number;
406
+ /** 发送缓冲高水位(字节,默认 8 MiB):`ws.bufferedAmount` 超过即以领域错误关闭慢客户端。 */
407
+ readonly maxSendBufferBytes?: number;
408
+ }
409
+ /** Audio ticket 绑定的能力授权(消费时原样返回,供服务端交叉校验 start 帧)。 */
410
+ interface AudioTicketGrant {
411
+ /** 允许的操作 */
412
+ readonly operation?: 'transcribe' | 'synthesize';
413
+ /** 允许的模型 ID */
414
+ readonly model?: string;
415
+ /** 绑定的会话 ID */
416
+ readonly sessionId?: string;
417
+ /** 应用自定义绑定字段 */
418
+ readonly [key: string]: unknown;
419
+ }
420
+ /** Audio ticket 验证结果:IAM 会话 + 可选能力授权。 */
421
+ interface AudioTicketVerification {
422
+ /** IAM 会话摘要 */
423
+ readonly session: ServSession;
424
+ /** 票据绑定的能力授权(如有) */
425
+ readonly grant?: AudioTicketGrant;
426
+ }
427
+ /** Audio WebSocket 经应用授权后允许生效的服务端参数。 */
428
+ interface AuthorizedAudioRequest {
429
+ readonly operation: 'transcribe' | 'synthesize';
430
+ /** 服务端允许的模型 ID;不传时使用 `ai.audio` 对应操作的默认模型。 */
431
+ readonly model?: string;
432
+ readonly voice?: string;
433
+ readonly instruction?: string;
434
+ readonly format?: AudioFormat;
435
+ readonly sampleRate?: number;
365
436
  }
366
437
  /**
367
438
  * 创建并装配 HTTP API app。
@@ -735,4 +806,4 @@ declare const serv: {
735
806
  m: (key: "$schema" | "serv_validationFailed" | "serv_validationRequired" | "serv_validationInvalid" | "serv_validationInvalidType" | "serv_validationStringMin" | "serv_validationStringMax" | "serv_validationNumberMin" | "serv_validationNumberMax" | "serv_validationArrayMin" | "serv_validationArrayMax" | "serv_validationTooSmall" | "serv_validationTooBig" | "serv_validationEmail" | "serv_validationUrl" | "serv_validationUuid" | "serv_validationEnum" | "serv_errorUnauthorized" | "serv_errorForbidden" | "serv_errorInternal" | "serv_storageKeyRequired" | "serv_storageKeyTooLong" | "serv_storageKeyIllegalChars" | "serv_storageKeyDotSegments" | "serv_iamCannotDeleteCurrentUser" | "serv_transportInvalidPayload" | "serv_transportUnknownClient" | "serv_transportClientIdRequired" | "serv_transportClientKeyNotFound" | "serv_transportDecryptFailed" | "serv_transportEncryptFailed" | "serv_transportKeyExchangeFailed", options?: _h_ai_core.MessageOptions) => string;
736
807
  };
737
808
 
738
- export { type CreateServAppOptions, CreateServContext, type GenerateOpenAPISpecOptions, RefreshCookieConfig, type ServAudioConfig, type ServConfig, type ServConfigInput, ServConfigSchema, ServContext, type ServCorsConfig, type ServDocsHttpConfig, type ServFetchHandler, type ServGuardedProcedureWrapper, type ServHealthHttpConfig, type ServHttpApp, type ServHttpConfig, type ServHttpConfigInput, ServIam, type ServListenOptions, type ServMiddleware, type ServMiddlewareFactory, type ServMiddlewareMount, type ServNodeServer, type ServOpenAPIHttpConfig, type ServProcedureHandler, type ServProcedureOptions, type ServProcedureWrapper, type ServRpcHttpConfig, ServSession, type ServTransportConfig, type ServTransportRuntimeConfig, type ServTransportRuntimeConfigInput, type ServValidationFailureBody, serv };
809
+ export { type AudioTicketGrant, type AudioTicketVerification, type AuthorizedAudioRequest, type CreateServAppOptions, CreateServContext, type GenerateOpenAPISpecOptions, RefreshCookieConfig, type ServAudioConfig, type ServConfig, type ServConfigInput, ServConfigSchema, ServContext, type ServCorsConfig, type ServDocsHttpConfig, type ServFetchHandler, type ServGuardedProcedureWrapper, type ServHealthHttpConfig, type ServHttpApp, type ServHttpConfig, type ServHttpConfigInput, ServIam, type ServListenOptions, type ServMiddleware, type ServMiddlewareFactory, type ServMiddlewareMount, type ServNodeServer, type ServOpenAPIHttpConfig, type ServProcedureHandler, type ServProcedureOptions, type ServProcedureWrapper, type ServRpcHttpConfig, ServSession, type ServTransportConfig, type ServTransportRuntimeConfig, type ServTransportRuntimeConfigInput, type ServValidationFailureBody, serv };