@h-ai/cli 0.1.0-alpha.67 → 0.1.0-alpha.68

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.
Files changed (164) hide show
  1. package/README.en.md +40 -4
  2. package/README.ja.md +40 -4
  3. package/README.md +40 -4
  4. package/dist/{src-2V6VP4I5.js → chunk-FI56GONY.js} +32 -3103
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-7DSURTJ4.js → chunk-ZFNBUF77.js} +327 -199
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-6AHNUGBN.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-FC3QRKDO.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-FC3QRKDO.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +920 -336
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/apps/admin/messages/ja-JP.json +38 -0
  21. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  22. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  23. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  24. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  25. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +5 -1
  26. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  27. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  28. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  29. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  30. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  31. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  32. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  33. package/templates/apps/api/package.json.hbs +1 -1
  34. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  35. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +1 -1
  36. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  37. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  38. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +1 -1
  39. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +1 -1
  40. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  41. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  42. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
  44. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  45. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +1 -1
  46. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +19 -8
  47. package/templates/apps/fullstack/package.json.hbs +1 -1
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  49. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  50. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  51. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +5 -2
  52. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  53. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  54. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  55. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  56. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  57. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  58. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  59. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  60. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  61. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  62. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  63. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +9 -4
  64. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  65. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  66. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +5 -5
  67. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  68. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +1 -1
  69. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  70. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +11 -12
  71. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  72. package/templates/apps/h5/messages/ja-JP.json +40 -0
  73. package/templates/apps/h5/src/routes/+layout.svelte.hbs +14 -3
  74. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  75. package/templates/apps/mobile-app/src/App.svelte.hbs +13 -3
  76. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  77. package/templates/apps/website/messages/ja-JP.json +32 -0
  78. package/templates/apps/website/src/routes/+layout.svelte.hbs +10 -2
  79. package/templates/base/e2e/app.spec.ts.hbs +13 -8
  80. package/templates/base/package.json.hbs +4 -1
  81. package/templates/base/playwright.config.ts +15 -4
  82. package/templates/base/src/hooks.server.ts.hbs +15 -6
  83. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  84. package/templates/base/vite.config.ts.hbs +2 -2
  85. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  86. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  87. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  88. package/templates/i18n/project.inlang/settings.json +1 -1
  89. package/templates/shared/e2e-env.ts +98 -0
  90. package/templates/shared/run-e2e.mjs +51 -12
  91. package/templates/skills/AGENTS.md +4 -0
  92. package/templates/skills/bridges/admin/AGENTS.md +5 -1
  93. package/templates/skills/bridges/api/AGENTS.md +2 -0
  94. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +4 -0
  95. package/templates/skills/bridges/h5/AGENTS.md +5 -1
  96. package/templates/skills/bridges/mobile-app/AGENTS.md +5 -1
  97. package/templates/skills/bridges/website/AGENTS.md +5 -1
  98. package/templates/skills/hai-ai/SKILL.md +8 -2
  99. package/templates/skills/hai-ai/guide.md +134 -60
  100. package/templates/skills/hai-ai/reference.md +51 -11
  101. package/templates/skills/hai-api-client/SKILL.md +2 -0
  102. package/templates/skills/hai-api-client/reference.md +17 -5
  103. package/templates/skills/hai-api-contract/SKILL.md +1 -0
  104. package/templates/skills/hai-api-contract/reference.md +4 -2
  105. package/templates/skills/hai-app-create/SKILL.md +4 -0
  106. package/templates/skills/hai-app-create/development.md +20 -4
  107. package/templates/skills/hai-app-create/reference.md +2 -1
  108. package/templates/skills/hai-app-review/SKILL.md +6 -2
  109. package/templates/skills/hai-app-review/differential.md +34 -0
  110. package/templates/skills/hai-app-tests/SKILL.md +3 -1
  111. package/templates/skills/hai-app-tests/reference.md +4 -4
  112. package/templates/skills/hai-app-tests/validation.md +10 -3
  113. package/templates/skills/hai-audit/SKILL.md +10 -8
  114. package/templates/skills/hai-audit/reference.md +3 -0
  115. package/templates/skills/hai-build/reference.md +6 -4
  116. package/templates/skills/hai-cache/SKILL.md +9 -3
  117. package/templates/skills/hai-cache/reference.md +53 -11
  118. package/templates/skills/hai-capacitor/SKILL.md +3 -1
  119. package/templates/skills/hai-capacitor/reference.md +9 -5
  120. package/templates/skills/hai-core/SKILL.md +4 -0
  121. package/templates/skills/hai-core/reference.md +25 -5
  122. package/templates/skills/hai-crypto/SKILL.md +10 -2
  123. package/templates/skills/hai-crypto/reference.md +64 -10
  124. package/templates/skills/hai-datapipe/SKILL.md +2 -0
  125. package/templates/skills/hai-datapipe/reference.md +3 -1
  126. package/templates/skills/hai-deploy/SKILL.md +9 -4
  127. package/templates/skills/hai-deploy/middleware.md +51 -37
  128. package/templates/skills/hai-iam/SKILL.md +24 -8
  129. package/templates/skills/hai-iam/reference.md +181 -154
  130. package/templates/skills/hai-kit/SKILL.md +23 -7
  131. package/templates/skills/hai-kit/reference.md +42 -24
  132. package/templates/skills/hai-payment/SKILL.md +13 -11
  133. package/templates/skills/hai-payment/reference.md +80 -41
  134. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  135. package/templates/skills/hai-reach/SKILL.md +5 -2
  136. package/templates/skills/hai-reach/reference.md +83 -22
  137. package/templates/skills/hai-reldb/SKILL.md +5 -2
  138. package/templates/skills/hai-reldb/reference.md +68 -25
  139. package/templates/skills/hai-scheduler/SKILL.md +8 -5
  140. package/templates/skills/hai-scheduler/reference.md +38 -24
  141. package/templates/skills/hai-serv/SKILL.md +23 -8
  142. package/templates/skills/hai-serv/reference.md +123 -87
  143. package/templates/skills/hai-spec-implement/SKILL.md +4 -0
  144. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  145. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  146. package/templates/skills/hai-spec-implement/templates.md +5 -0
  147. package/templates/skills/hai-spec-implement/workflow.md +2 -0
  148. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  149. package/templates/skills/hai-storage/SKILL.md +8 -2
  150. package/templates/skills/hai-storage/reference.md +64 -25
  151. package/templates/skills/hai-ui/SKILL.md +16 -2
  152. package/templates/skills/hai-ui/guide.md +3 -3
  153. package/templates/skills/hai-ui/reference.md +17 -6
  154. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  155. package/templates/skills/hai-ui-design/patterns.md +26 -0
  156. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  157. package/templates/skills/hai-ui-review/checklist.md +22 -0
  158. package/templates/skills/hai-vecdb/SKILL.md +12 -8
  159. package/templates/skills/hai-vecdb/reference.md +33 -3
  160. package/dist/chunk-7DSURTJ4.js.map +0 -1
  161. package/dist/chunk-AOQ4N657.js +0 -640
  162. package/dist/chunk-AOQ4N657.js.map +0 -1
  163. package/dist/cli-config-templates-6AHNUGBN.js +0 -5
  164. package/dist/src-2V6VP4I5.js.map +0 -1
@@ -36,7 +36,7 @@
36
36
 
37
37
  ## 架构边界
38
38
 
39
- - 页面内容与导航文案全部走 i18n,同步中英文 messages。
39
+ - 页面内容与导航文案全部走 i18n,同步中英日文 messages。
40
40
  - 表单和 API 输入必须 Zod 校验。
41
41
  - UI 组件优先使用 `@h-ai/ui`,不要重复实现已有组件。
42
42
  - `+server.ts` 只能导出 handler 和 SvelteKit 允许的配置项;helper 放到 `src/lib/**`。
@@ -80,3 +80,7 @@ pnpm test:e2e
80
80
  - `hai-build`、`hai-app-create`、`hai-app-review`、`hai-app-tests`
81
81
  - `hai-kit`、`hai-ui`、`hai-reldb`、`hai-cache`
82
82
  - 其它 `hai-*` 模块按需读取
83
+
84
+ 前端页面先按 [hai-ui-design](.agents/skills/hai-ui-design/SKILL.md) 确定任务、布局、状态与组件映射,使用 [hai-ui](.agents/skills/hai-ui/SKILL.md) 实现,再由 [hai-ui-review](.agents/skills/hai-ui-review/SKILL.md) 检查真实页面。窄范围修改复用已有设计;按实际语言/主题/尺寸验证。
85
+
86
+ 工作流技能按 hai-领域-动作 命名,能力使用类沿用领域名。需求、验收、发现与当前版本证据按 [工作交接契约](.agents/skills/hai-spec-implement/handoff.md) 传递,沿既有清单恢复任务。
@@ -13,12 +13,14 @@ description: "接入 @h-ai/ai 的 LLM、工具/MCP、RAG、会话记忆、图像
13
13
  | 适用场景 | AI 应用服务端集成或浏览器代理接入 |
14
14
  | 输入 | AIConfigInput、消息/音频/图片、scope、AbortSignal |
15
15
  | 输出 | 领域操作为 HaiResult;流为 AsyncIterable,迭代可抛错 |
16
- | 限制 | tools/stream 无需 init;其他领域先初始化。持久化需先初始化 reldb + vecdb 或传 storeProvider;临时 Store 关闭后丢失。工具调用仍需业务授权。 |
16
+ | 限制 | tools/stream 无需 init;其他领域先初始化。生产须先初始化共享 PostgreSQL/MySQL + 网络 vecdb 或传 stateScope=shared 的 Provider;临时 Store 仅开发/测试,关闭后丢失。工具调用仍需业务授权。 |
17
17
 
18
18
  ## 使用路径
19
19
 
20
20
  - 先选择完整结果还是流式 API;不要对 AsyncIterable 取 `.success`。流取消/错误须结束播放器和连接。
21
- - Memory/Context 用 objectId/sessionId 隔离主体;手动 turnCommit 只提交实际播放/发生的文本。
21
+ - Memory/Context 用 objectId/sessionId 隔离主体;Context scope 必填,跨节点读先 reload/restore,manual pending 可跨节点按 turnId 幂等提交实际文本。共享 Store 必须实现版本 CAS/存储端期限;过期生成不自动重放外部副作用。
22
+ - Mem0 生产须实际映射持久 Qdrant;内存回退开关不能绕过。列表/条件清理最多 1000 条主体原始候选,超限检查 `MEMORY_LIST_INCOMPLETE`;禁止把截断结果当完整 total。
23
+ - HTTP AI 使用 `@h-ai/api-client` 的 typed 领域方法;A2A 由应用定义并绑定自己的 contract;`@h-ai/ai/client` 只提供独立 `createAudioClient`,不再封装字符串路径 HTTP 或重复 SSE helper。
22
24
  - 语音客户端用一次性 ticket,不能把 IAM access token 放 URL;按 segment_started 的真实格式解码。
23
25
  - 压缩超过硬预算须停止模型请求;不能失败后发送原始超长消息。
24
26
 
@@ -42,3 +44,7 @@ description: "接入 @h-ai/ai 的 LLM、工具/MCP、RAG、会话记忆、图像
42
44
  - 质量与安全检查
43
45
 
44
46
  补充类型/错误码与范本见 [reference.md](reference.md),仅在上述用法不足时读取。
47
+
48
+ 公开 API 示例以安装包的实际入口为准;框架源码中的内部工厂例子仅供模块维护。HaiResult 调用先检查 success,流与浏览器 AudioClient 按显式抛错契约处理;Context flush 失败和 A2A 未知外部效果不得盲目重放。
49
+
50
+ Node 自定义 SSE 使用 ai.stream 的统一编解码,不用 split/trim 手拆上游事件;encoder 的非法元数据会显式抛错。Anthropic/MiMo 的真实 HTTP 流在退出和取消时释放正文,EOF 不伪造未完成事件,不自动重连或重放业务。
@@ -8,19 +8,20 @@
8
8
 
9
9
  - `ai.tools`、`ai.stream` 是纯函数能力,无需 `ai.init()`。
10
10
  - `ai.llm`、`ai.embedding`、`ai.memory`、`ai.retrieval`、`ai.rag`、`ai.knowledge`、`ai.context`、`ai.image`、`ai.audio`、`ai.a2a` 需要先 `await ai.init(...)`。
11
- - 未初始化 reldb/vecdb 时,`ai.init()` 使用进程内临时 Store,适合 LLM-only 与本地原型;数据在 `ai.close()` 或进程退出后丢失。
12
- - reldb 与 vecdb 均已初始化时自动使用持久化 DB Provider;生产 Memory/Context/Persona/Knowledge 应使用该路径或自定义 `AIStoreProvider`。
11
+ - 开发/显式测试未初始化 reldb/vecdb 时,`ai.init()` 使用临时 Store;关闭/进程退出后丢失。
12
+ - `NODE_ENV` 或 `HAI_CORE_ENV=production` 要求共享 Store,内置实现先初始化 PostgreSQL/MySQL 与网络 vecdb;自定义 `AIStoreProvider` 必须如实声明 stateScope=shared,不能把本机后端作为生产共享状态。
13
13
  - 浏览器端不要直接调用 Node-only 能力;通过 `@h-ai/api-client` 或应用自定义 API/SSE endpoint 代理。
14
14
  - 公共 API 返回 `HaiResult<T>` 时按 `if (!result.success) return result` 处理;不要用 `try/catch` 包裹正常业务错误。
15
15
 
16
16
  ## 初始化与关闭
17
17
 
18
18
  ```ts
19
- import { core } from '@h-ai/core'
20
19
  import { ai } from '@h-ai/ai'
20
+ import { core } from '@h-ai/core'
21
21
 
22
22
  const init = await ai.init(core.config.get('ai'))
23
- if (!init.success) return init
23
+ if (!init.success)
24
+ return init
24
25
 
25
26
  // LLM-only:未初始化 DB 时使用进程内临时 Store
26
27
  // ... 使用 ai.llm
@@ -28,17 +29,48 @@ if (!init.success) return init
28
29
  await ai.close()
29
30
  ```
30
31
 
31
- 需要持久化时先初始化 DB:
32
+ 需要持久化时先校验三份配置,再按依赖顺序初始化。此片段位于应用启动生命周期;应用统一关闭管理器负责释放已获取的依赖,初始化失败时不接收业务请求。
32
33
 
33
34
  ```ts
35
+ import { ai, AIConfigSchema } from '@h-ai/ai'
36
+ import { core } from '@h-ai/core'
37
+ import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
38
+ import { vecdb, VecdbConfigSchema } from '@h-ai/vecdb'
39
+
40
+ const databaseConfig = core.config.validate('db', ReldbConfigSchema)
41
+ if (!databaseConfig.success)
42
+ return databaseConfig
43
+ const vectorConfig = core.config.validate('vecdb', VecdbConfigSchema)
44
+ if (!vectorConfig.success)
45
+ return vectorConfig
46
+ const aiConfig = core.config.validate('ai', AIConfigSchema)
47
+ if (!aiConfig.success)
48
+ return aiConfig
49
+ const database = await reldb.init(databaseConfig.data)
50
+ if (!database.success)
51
+ return database
52
+ const vectors = await vecdb.init(vectorConfig.data)
53
+ if (!vectors.success)
54
+ return vectors
55
+ return await ai.init(aiConfig.data)
56
+ ```
57
+
58
+ 应用的关闭生命周期按依赖反序执行。失败时保留下层依赖并报告错误,待修正后重试关闭;业务异常也须进入同一个关闭管理器。
59
+
60
+ ```ts
61
+ import { ai } from '@h-ai/ai'
34
62
  import { reldb } from '@h-ai/reldb'
35
63
  import { vecdb } from '@h-ai/vecdb'
36
64
 
37
- await reldb.init(core.config.get('db'))
38
- await vecdb.init(core.config.get('vecdb'))
39
- await ai.init(core.config.get('ai'))
65
+ await ai.close() // AI 按实际 Promise<void> 契约关闭
66
+ const vectorsClosed = await vecdb.close()
67
+ if (!vectorsClosed.success)
68
+ return vectorsClosed
69
+ return await reldb.close()
40
70
  ```
41
71
 
72
+ MySQL 首次初始化允许多个节点并发建索引:仅接受 mysql2 明确的 ER_DUP_KEYNAME/1061,并重新核对列及顺序、非唯一完整列、升序可见 BTREE 和无表达式;已有同名错误结构仍失败。账户需要建表和 INDEX 权限,元数据读取、权限或其他 DDL 错误使 ai.init 返回 CONFIGURATION_ERROR。返回前等待全部已启动 DDL 结束;MySQL DDL 不回滚,失败保留已创建结构,修复权限/结构后再重试。逐步检查初始化结果,失败不接收业务请求,并逆序关闭 AI、vecdb、reldb。
73
+
42
74
  自定义存储:
43
75
 
44
76
  ```ts
@@ -57,7 +89,7 @@ llm:
57
89
  timeout: 60000
58
90
  tempModelCacheTtl: 600000 # 临时模型客户端缓存 TTL(毫秒,默认 10 分钟)
59
91
  models: # 可为单个模型指定协议(responses / anthropic)
60
- - { id: claude, model: claude-3-5-sonnet-latest, api: anthropic }
92
+ - {id: claude, model: claude-3-5-sonnet-latest, api: anthropic}
61
93
  scenarios:
62
94
  chat: fast
63
95
  reasoning: strong
@@ -79,7 +111,7 @@ knowledge:
79
111
  memory:
80
112
  provider: mem0
81
113
  defaultTopK: 10
82
- # allowEphemeralFallback: true # 仅明确接受重启丢失数据时开启
114
+ # allowEphemeralFallback: true # 仅开发/明确 E2E,生产禁止实际内存回退
83
115
  ```
84
116
 
85
117
  约定环境变量优先于 YAML,例如 `HAI_AI_LLM_APIKEY`、
@@ -89,31 +121,31 @@ memory:
89
121
 
90
122
  ## 常用 API
91
123
 
92
- | 场景 | 首选 API | 备注 |
93
- | --- | --- | --- |
94
- | 非流式对话 | `ai.llm.chat({ messages })` | OpenAI 兼容 Chat Completion |
95
- | 流式对话 | `ai.llm.chatStream({ messages })` | 返回 `AsyncIterable` |
96
- | 多协议 | 模型 `api: chat/responses/anthropic` | 底层走 Chat Completions / Responses / Anthropic,公共形状不变 |
97
- | 请求取消 | `ai.llm.chat({ messages, signal })` | 传 `AbortSignal`,`abort()` 立即停上游生成 |
98
- | 临时模型 | `ai.llm.chat({ messages, tempModel })` | 单次请求级临时端点,客户端按 TTL 缓存 |
99
- | 工具定义 | `ai.tools.define(...)` | Zod schema 转 JSON Schema;`handler(input, ctx)` 第二参为执行上下文 |
100
- | 工具注册 | `registry.register/replace` | `register` 默认拒绝同名并返回 HaiResult;只有 `replace` 可显式替换 |
101
- | 工具执行 | `registry.execute(tc, { signal, objectId, sessionId, deadline?, timeoutMs?, authorize? })` / `executeAll(...)` | Registry/单次授权必须同时通过;默认串行,支持取消/超时;只有确认无副作用时才 `parallel: true` |
102
- | MCP 服务 | `createMcpServer(...)` | 按需连接 HTTP/SSE/Stdio transport |
103
- | Embedding | `ai.embedding.embedText(text)` | 批量用 `embedBatch` |
104
- | 记忆 | `ai.memory.extract/recall/injectMemories` | `extract(..., { signal })` 可取消整条提取链;用 `objectId` 做主体隔离,`scope` 做业务作用域隔离;scope 隔离越细,`candidateMultiplier` 调大以防漏召回 |
105
- | 记忆作用域 | `ai.memory.scoped({ objectId, scope })` | **多租户推荐入口**:所有操作自动绑定 objectId/scope(含归属校验);`clear` 仅清作用域内 |
106
- | 记忆管理 | `ai.memory.admin.clearAll({ confirm: true })` | 唯一的全局清空入口(需显式确认);`ai.memory.clear()` 空过滤会被拒绝,防误清全局 |
107
- | 角色人格 | `ai.persona.save/get/compose` | 定义 AI 身份(systemPrompt + traits),`compose` 组合系统提示词;按 `objectId` 隔离多租户(不传默认 `system` 平台角色),`scope: { personaId }` 关联长期记忆 |
108
- | Retrieval/RAG | `ai.retrieval.retrieve` / `ai.rag.query` | Retrieval source 先注册或由配置预置 |
109
- | Knowledge | `ai.knowledge.setup/ingest/ask` | 入库前会调用 datapipe 清洗分块 |
110
- | Context | `ai.context.createManager` | 编排 LLM + Memory + RAG + 压缩;`manager.consolidate()` 把会话固化为长期记忆 |
111
- | Context 并发 | `createManager({ concurrency: 'reject' \| 'queue' })` | 默认单活动生成防消息乱序;活动生成期间新 chat 返回 `CONTEXT_BUSY`(reject)或排队(queue) |
112
- | Context 重置 | `await manager.reset({ preserveSystemPrompt?, cancelActiveTurn?, waitForMemoryTasks? })` | 异步:终止活动生成、清空消息/摘要/轮次,默认保留系统提示词 |
113
- | 语音识别 | `ai.audio.transcribe` / `transcribeStream` | 完整音频返回 `HaiResult`;持续音频输入用 `AudioInputStream` 流式返回临时文本 |
114
- | 语音合成 | `ai.audio.synthesize` / `synthesizeStream` | `text` 可为字符串或 `AsyncIterable<string>`(可接 LLM 文本流边生成边合成);完整结果可用 `serializePlayableAudio` 转为浏览器播放负载 |
115
- | 图片生成 | `ai.image.generate({ prompt, size?, model?, referenceImages? })` | 可选参考图使用字节 + MIME;Provider 协议和临时 URL 下载由模块内部处理 |
116
- | A2A | `ai.a2a.registerExecutor/handleRequest` | 延迟初始化 SDK handler |
124
+ | 场景 | 首选 API | 备注 |
125
+ | ------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
126
+ | 非流式对话 | `ai.llm.chat({ messages })` | OpenAI 兼容 Chat Completion |
127
+ | 流式对话 | `ai.llm.chatStream({ messages })` | 返回 `AsyncIterable` |
128
+ | 多协议 | 模型 `api: chat/responses/anthropic` | 底层走 Chat Completions / Responses / Anthropic,公共形状不变 |
129
+ | 请求取消 | `ai.llm.chat({ messages, signal })` | 传 `AbortSignal`,`abort()` 立即停上游生成 |
130
+ | 临时模型 | `ai.llm.chat({ messages, tempModel })` | 单次请求级临时端点,客户端按 TTL 缓存 |
131
+ | 工具定义 | `ai.tools.define(...)` | Zod schema 转 JSON Schema;`handler(input, ctx)` 第二参为执行上下文 |
132
+ | 工具注册 | `registry.register/replace` | `register` 默认拒绝同名并返回 HaiResult;只有 `replace` 可显式替换 |
133
+ | 工具执行 | `registry.execute(tc, { signal, objectId, sessionId, deadline?, timeoutMs?, authorize? })` / `executeAll(...)` | Registry/单次授权必须同时通过;默认串行,支持取消/超时;只有确认无副作用时才 `parallel: true` |
134
+ | MCP 服务 | `createMcpServer(...)` | 按需连接 HTTP/SSE/Stdio transport |
135
+ | Embedding | `ai.embedding.embedText(text)` | 批量用 `embedBatch` |
136
+ | 记忆 | `ai.memory.extract/recall/injectMemories` | `extract(..., { signal })` 可取消整条提取链;用 `objectId` 做主体隔离,`scope` 做业务作用域隔离;scope 隔离越细,`candidateMultiplier` 调大以防漏召回 |
137
+ | 记忆作用域 | `ai.memory.scoped({ objectId, scope })` | **多租户推荐入口**:所有操作自动绑定 objectId/scope(含归属校验);`clear` 仅清作用域内 |
138
+ | 记忆管理 | `ai.memory.admin.clearAll({ confirm: true })` | 唯一的全局清空入口(需显式确认);`ai.memory.clear()` 空过滤会被拒绝,防误清全局 |
139
+ | 角色人格 | `ai.persona.save/get/compose` | 定义 AI 身份(systemPrompt + traits),`compose` 组合系统提示词;按 `objectId` 隔离多租户(不传默认 `system` 平台角色),`scope: { personaId }` 关联长期记忆 |
140
+ | Retrieval/RAG | `ai.retrieval.retrieve` / `ai.rag.query` | Retrieval source 先注册或由配置预置 |
141
+ | Knowledge | `ai.knowledge.setup/ingest/ask` | 入库前会调用 datapipe 清洗分块 |
142
+ | Context | `ai.context.createManager` | 编排 LLM + Memory + RAG + 压缩;`manager.consolidate()` 把会话固化为长期记忆 |
143
+ | Context 并发 | `createManager({ scope, concurrency: 'reject' \| 'queue' })` | 默认单活动生成防消息乱序;活动生成期间新 chat 返回 `CONTEXT_BUSY`(reject)或排队(queue) |
144
+ | Context 重置 | `await manager.reset({ preserveSystemPrompt?, cancelActiveTurn?, waitForMemoryTasks? })` | 异步:终止活动生成、清空消息/摘要/轮次,默认保留系统提示词 |
145
+ | 语音识别 | `ai.audio.transcribe` / `transcribeStream` | 完整音频返回 `HaiResult`;持续音频输入用 `AudioInputStream` 流式返回临时文本 |
146
+ | 语音合成 | `ai.audio.synthesize` / `synthesizeStream` | `text` 可为字符串或 `AsyncIterable<string>`(可接 LLM 文本流边生成边合成);完整结果可用 `serializePlayableAudio` 转为浏览器播放负载 |
147
+ | 图片生成 | `ai.image.generate({ prompt, size?, model?, referenceImages? })` | 可选参考图使用字节 + MIME;Provider 协议和临时 URL 下载由模块内部处理 |
148
+ | A2A | `ai.a2a.registerExecutor/handleRequest` | 延迟初始化 SDK handler |
117
149
 
118
150
  ## LLM + 工具调用
119
151
 
@@ -129,7 +161,8 @@ const registered = registry.register(ai.tools.define({
129
161
  parameters: z.object({ city: z.string() }),
130
162
  handler: async ({ city }) => ({ city, temperature: 20 }),
131
163
  }))
132
- if (!registered.success) return registered
164
+ if (!registered.success)
165
+ return registered
133
166
 
134
167
  const messages: ChatMessage[] = [{ role: 'user', content: '北京天气?' }]
135
168
  const objectId = 'user-001'
@@ -141,7 +174,8 @@ while (result.success && result.data.choices[0]?.finish_reason === 'tool_calls')
141
174
  messages.push(assistant)
142
175
 
143
176
  const toolMessages = await registry.executeAll(toolCalls, { objectId })
144
- if (!toolMessages.success) return toolMessages
177
+ if (!toolMessages.success)
178
+ return toolMessages
145
179
  messages.push(...toolMessages.data)
146
180
 
147
181
  result = await ai.llm.chat({ messages, tools: registry.getDefinitions() })
@@ -154,7 +188,8 @@ while (result.success && result.data.choices[0]?.finish_reason === 'tool_calls')
154
188
  const processor = ai.stream.createProcessor()
155
189
  for await (const chunk of ai.llm.chatStream({ messages })) {
156
190
  const delta = processor.process(chunk)
157
- if (delta?.content) process.stdout.write(delta.content)
191
+ if (delta?.content)
192
+ process.stdout.write(delta.content)
158
193
  }
159
194
  const message = processor.toAssistantMessage()
160
195
  ```
@@ -202,14 +237,24 @@ await server.connect(new StreamableHTTPServerTransport({ sessionIdGenerator: cor
202
237
 
203
238
  ## 记忆、RAG 与知识库
204
239
 
205
- `memory.provider` 默认为 `native`(推荐):HAI 原生引擎,复用 vecdb/reldb/LLM/Embedding,`extract` 采用 **Mem0 式批量合并**(一次 LLM 调用对整批事实与相关既有记忆做 ADD/UPDATE/DELETE/NONE,支持 `category`)。`mem0` 则直接使用 `mem0ai/oss` 的 `Memory` 引擎:LLM/Embedder 从 `llm` 配置提取(OpenAI 兼容),向量库从底层 vecdb 后端提取(qdrant/pgvector 复用)。**mem0 无法映射 lancedb/chroma 等后端时默认 fail-fast**(初始化失败,避免重启后记忆静默丢失);只有显式 `memory.allowEphemeralFallback: true` 才退回 mem0 自带 in-memory 存储。两者对外 `ai.memory.*` API 完全一致。
240
+ `memory.provider` 默认为 `native`(推荐):HAI 原生引擎,复用 vecdb/reldb/LLM/Embedding,`extract` 采用 **Mem0 式批量合并**(一次 LLM 调用对整批事实与相关既有记忆做 ADD/UPDATE/DELETE/NONE,支持 `category`)。`mem0` 则直接使用 `mem0ai/oss` 的 `Memory` 引擎:LLM/Embedder 从 `llm` 配置提取(OpenAI 兼容),向量库从底层 vecdb 后端提取(qdrant 复用)。当前适配仅映射带完整连接参数的 `qdrant`;生产环境(`NODE_ENV` 或 `HAI_CORE_ENV` 为 `production`)即使设置 `memory.allowEphemeralFallback: true` 也拒绝实际内存回退。临时回退仅限开发或明确的 `HAI_E2E=1` / `HAI_E2E_MOCK=1`;缺少后端同样不能在生产启动。两者提供同一组 `ai.memory.*` 操作。
241
+
242
+ Mem0 的 `update` 使用 SDK 原地修改文本与元数据,保持 `id` 和创建时间。SDK 查询使用 `filters.user_id`,写入和整主体删除使用 `userId`。SDK 无列表游标:`list` / `listPage` / 条件 `clear` 最多支持每主体 1000 条原始候选(含过期条目),多取一条探测完整性;超限返回 `MEMORY_LIST_INCOMPLETE`(906),不会伪造 total 或执行部分条件删除。候选完整时按 UTC 日期剔除过期项,再按类型/scope 过滤和分页;仅 `objectId` 的清理使用 SDK `deleteAll`,不受列表上限限制。公共空条件清理被拒绝;跨主体全清仅限受信管理员显式 `admin.clearAll({ confirm: true })`。
243
+
244
+ Mem0 3.3.1 的 Qdrant 适配仍依赖 `QdrantClient.search`;项目将 `@qdrant/js-client-rest` 精确锁定为受测的 `1.18.0`,AI 的可选 peer 也声明此版本。启动会检查该公开方法,缺失时明确配置失败;不要覆盖为已移除此方法的 1.19。
245
+
246
+ Mem0 的 pgvector 组合在所有环境拒绝启动:当前 SDK 将 PostgreSQL 连接保存在私有对象中,未提供公开关闭入口,框架无法履行资源清理契约。需要 pgvector 时选择 `memory.provider: native`;该原生后端保持支持。`disableHistory: true` 使用 SDK 无资源的历史占位实现,不创建历史 SQLite 文件。工厂通过公开的一条有界只读查询等待实际后端 ready 后才发布成功。
247
+
248
+ `add` / `extract` 的 SDK 回执不含完整业务字段,框架写后读取权威记录再返回;并发提取等待全部在途写入结束。若写入或写后读失败,`error.ext.requiresEffectReconciliation=true`,`memoryIds` 保存已知写入 ID(回执丢失时可能为空)。应按主体核对实际记忆后决定恢复,不能盲目重做 add/extract;框架不自动重放。
206
249
 
207
250
  ```ts
208
251
  const enriched = await ai.memory.injectMemories(messages, { objectId: 'user-001', topK: 5 })
209
- if (!enriched.success) return enriched
252
+ if (!enriched.success)
253
+ return enriched
210
254
 
211
255
  const compressed = await ai.compress.tryCompress(enriched.data, { strategy: 'hybrid', maxTokens: 4000 })
212
- if (!compressed.success) return compressed
256
+ if (!compressed.success)
257
+ return compressed
213
258
 
214
259
  const answer = await ai.rag.query('核心架构是什么?', { sources: ['docs'], topK: 5 })
215
260
  ```
@@ -220,7 +265,8 @@ const answer = await ai.rag.query('核心架构是什么?', { sources: ['docs'
220
265
 
221
266
  ```ts
222
267
  const setup = await ai.knowledge.setup()
223
- if (!setup.success) return setup
268
+ if (!setup.success)
269
+ return setup
224
270
 
225
271
  const ingest = await ai.knowledge.ingest({
226
272
  documentId: 'doc-001',
@@ -228,13 +274,26 @@ const ingest = await ai.knowledge.ingest({
228
274
  title: '产品手册',
229
275
  enableEntityExtraction: true,
230
276
  })
231
- if (!ingest.success) return ingest
277
+ if (!ingest.success)
278
+ return ingest
279
+ if (ingest.data.cleanupPending) {
280
+ const repair = await ai.knowledge.repair({ offset: 0, limit: 100 })
281
+ if (!repair.success)
282
+ return repair
283
+ // 大集合继续分页,并将 repair 全页扫描纳入定期维护。
284
+ }
232
285
 
233
286
  const answer = await ai.knowledge.ask('核心架构是什么?')
234
287
  ```
235
288
 
236
289
  ## Context 管理器
237
290
 
291
+ `scope` 必填;共享 scope 用版本化聚合记录保存消息、摘要、owner、轮次和 pending。所有消息、对外 delta 与提交即时持久化。同步 getters 仅返回本管理器最近一次成功读写的独立快照,读取其他节点修改前调用 `await manager.reload()` 或 `restoreManager()`。`save()` 等待本节点记忆提取、刷新快照并同步目录,不能覆盖远端版本。`markTurnSpeaking` 为异步持久化方法。
292
+
293
+ 共享单活动生成租约为存储端时钟30秒,每10秒续租,最终 CAS 同时检查版本、owner 和截止时间。过期生成仅 interrupted,不自动重放 LLM/工具/记忆;manual pending 持久保存,可跨节点/重启按 turnId 提交。相同终态与真实文本重复提交幂等,内容冲突失败。queue 可取消轮询,不保证FIFO。reset 更换共享纪元,删除保留永久墓碑且 scope 不复用。外部 LLM/工具/本地后台记忆不与上下文跨系统原子提交;崩溃后的未知副作用必须由应用核对。
294
+
295
+ 自定义 `AIRelStore` 必须提供版本化读取、原子 CAS 和存储端 `currentTime()`;普通 save/saveMany 也推进版本。CAS 缺失版本只允许创建,notAfter 在最终持久化时严格检查,存储故障抛错不能当空数据;业务 HaiResult 边界负责转失败。默认 DB schema 含 version 列,不自动迁移旧表;SQLite 仅用于开发/测试,生产使用 PostgreSQL/MySQL。
296
+
238
297
  ```ts
239
298
  const managerResult = ai.context.createManager({
240
299
  scope: { objectId: 'user-001', sessionId: 'sess-001' },
@@ -242,10 +301,13 @@ const managerResult = ai.context.createManager({
242
301
  compress: { auto: true, strategy: 'hybrid', maxTokens: 8000 },
243
302
  memory: { enable: true, enableExtract: true },
244
303
  })
245
- if (!managerResult.success) return managerResult
304
+ if (!managerResult.success)
305
+ return managerResult
246
306
 
247
307
  const reply = await managerResult.data.chat('你好')
248
- await managerResult.data.save()
308
+ const saved = await managerResult.data.save()
309
+ if (!saved.success)
310
+ return saved
249
311
  ```
250
312
 
251
313
  ### 真实对话状态(Conversation Commit Layer)
@@ -254,13 +316,17 @@ await managerResult.data.save()
254
316
 
255
317
  ```ts
256
318
  const manager = ai.context.createManager({ turnCommit: 'manual', scope })
257
- if (!manager.success) return manager
319
+ if (!manager.success)
320
+ return manager
258
321
  const m = manager.data
259
322
  const controller = new AbortController()
260
323
  for await (const ev of m.chatStream('展开讲讲', { signal: controller.signal })) {
261
- if (ev.type === 'delta') feedTts(ev.text)
262
- else if (ev.type === 'done') await m.commitTurn(ev.turnId) // 完整提交
263
- else if (ev.type === 'cancelled') await m.interruptTurn(ev.turnId, { text: spokenSoFar }) // 只提交播放出去的部分
324
+ if (ev.type === 'delta')
325
+ feedTts(ev.text)
326
+ else if (ev.type === 'done')
327
+ await m.commitTurn(ev.turnId) // 完整提交
328
+ else if (ev.type === 'cancelled')
329
+ await m.interruptTurn(ev.turnId, { text: spokenSoFar }) // 只提交播放出去的部分
264
330
  }
265
331
  ```
266
332
 
@@ -277,8 +343,8 @@ audio:
277
343
  # 仅当语音服务与 LLM 确认共用同一密钥时启用
278
344
  inheritLlmApiKey: true
279
345
  models:
280
- - { id: asr, provider: qwen, model: qwen3-asr-flash-realtime, operations: [transcribe] }
281
- - { id: tts, provider: qwen, model: qwen3-tts-flash-realtime, operations: [synthesize] }
346
+ - {id: asr, provider: qwen, model: qwen3-asr-flash-realtime, operations: [transcribe]}
347
+ - {id: tts, provider: qwen, model: qwen3-tts-flash-realtime, operations: [synthesize]}
282
348
  transcribeModel: asr
283
349
  synthesizeModel: tts
284
350
  ```
@@ -286,7 +352,8 @@ audio:
286
352
  ```ts
287
353
  // 完整识别 / 合成(HaiResult)
288
354
  const t = await ai.audio.transcribe({ audio: { data: wavBytes, format: 'wav' }, language: 'zh' })
289
- if (!t.success) return t
355
+ if (!t.success)
356
+ return t
290
357
  const s = await ai.audio.synthesize({ text: '欢迎', voice: 'Cherry', format: 'pcm16' })
291
358
 
292
359
  // 实时识别:持续音频输入 → 增量文本(chunk.final 标记语句最终态,可直接覆盖临时文本)
@@ -321,7 +388,7 @@ if (!playable.success)
321
388
  - 流式方法是 `AsyncIterable`,迭代期间的连接/协议/上游错误会终止迭代(抛出),不返回 `HaiResult`。
322
389
  - `segment_started` 携带服务端解析 Provider 后的真实输出参数(`format` / `sampleRate?` / `channels?`);播放器据此解码,不按请求参数猜测格式。
323
390
  - 平台不支持的输入方式(如 OpenAI/MiMo 的持续音频输入)会抛 `AUDIO_UNSUPPORTED_INPUT`,不伪装成实时。
324
- - 浏览器/移动端经 `@h-ai/serv` 统一语音 WebSocket 入口访问;客户端用已登录 HTTP 请求获取短期一次性 ticket,`@h-ai/ai/client` 通过 `audio.getTicket` 建连,不把 IAM access token 放入 URL。浏览器客户端区分正常结束、取消(`AUDIO_CANCELLED`)与 `end` 前异常断连(`AUDIO_CONNECTION_FAILED`);服务端 error 帧保留领域错误码,`synthesize` 不返回未完成的部分音频。
391
+ - 浏览器/移动端经 `@h-ai/serv` 统一语音 WebSocket 入口访问;客户端用已登录 HTTP 请求获取短期一次性 ticket,`createAudioClient({ url, getTicket })` 通过独立配置的 `getTicket` 建连,不把 IAM access token 放入 URL。浏览器客户端区分正常结束、取消(`AUDIO_CANCELLED`)与 `end` 前异常断连(`AUDIO_CONNECTION_FAILED`);服务端 error 帧保留领域错误码,`synthesize` 不返回未完成的部分音频。
325
392
 
326
393
  ### 本地 CPU 模型服务
327
394
 
@@ -333,12 +400,14 @@ hai-framework 源码仓库提供 `pnpm model <name> --device cpu` 与 `pnpm mode
333
400
 
334
401
  ```yaml
335
402
  image:
403
+ maxImageBytes: 20971520 # 单张输出/参考图上限;最大100MiB
404
+ maxImages: 4 # 1–16,URL串行下载;模型timeout覆盖整个生成/下载
336
405
  models:
337
- - { id: free, provider: pollinations, model: zimage }
338
- - { id: openai, provider: openai, model: gpt-image-2 }
339
- - { id: google, provider: google, model: gemini-3.1-flash-image-preview }
340
- - { id: qwen, provider: qwen, model: qwen-image-2.0-pro }
341
- - { id: seedream, provider: seedream, model: doubao-seedream-5-0-260128 }
406
+ - {id: free, provider: pollinations, model: zimage}
407
+ - {id: openai, provider: openai, model: gpt-image-2}
408
+ - {id: google, provider: google, model: gemini-3.1-flash-image-preview}
409
+ - {id: qwen, provider: qwen, model: qwen-image-2.0-pro}
410
+ - {id: seedream, provider: seedream, model: doubao-seedream-5-0-260128}
342
411
  generateModel: free
343
412
  ```
344
413
 
@@ -350,7 +419,8 @@ const image = await ai.image.generate({
350
419
  size: { width: 1024, height: 1024 },
351
420
  referenceImages: [{ data: referenceBytes, mimeType: 'image/png' }],
352
421
  })
353
- if (!image.success) return image
422
+ if (!image.success)
423
+ return image
354
424
  await saveImage(image.data.images[0].data, image.data.images[0].mimeType)
355
425
  ```
356
426
 
@@ -367,7 +437,8 @@ await saveImage(image.data.images[0].data, image.data.images[0].mimeType)
367
437
  export const POST = kit.handler(async ({ request }) => {
368
438
  const data = await kit.validate.body(request, ChatSchema)
369
439
  const result = await ai.llm.chat({ messages: data.messages })
370
- if (!result.success) return kit.response.error(result.error.code, result.error.message)
440
+ if (!result.success)
441
+ return kit.response.error(result.error.code, result.error.message)
371
442
  return kit.response.ok(result.data)
372
443
  })
373
444
  ```
@@ -381,3 +452,6 @@ export const POST = kit.handler(async ({ request }) => {
381
452
  - 批量导入、批量 embedding 用批量 API,避免 await-in-loop。
382
453
  - 文件解析/OCR 失败按 `HaiAIError.FILE_*` 返回,不抛业务异常。
383
454
  - A2A 认证失败返回 `A2A_AUTH_FAILED`;`callRemoteAgent` 只依赖 `ai.init()`,无需注册本地 executor。远端 URL 仅接受 HTTP(S) 且不得内嵌凭据,应用仍须配置 origin 白名单与出口网络策略。不要记录 token、apiKey、私钥、完整 headers 或带 query 的 URL。
455
+ - A2A 入站日志保存并重验租约后才执行业务,出站日志在已有有界队列内完成后才发布;日志失败产生 failed/interrupted Task,不重放相同 receipt。日志不是交付回执,按 requiresEffectReconciliation 核对可能已执行的效果;listMessages 失败为安全的 A2A_LIST_MESSAGES_FAILED(984)。
456
+ - A2A 任务与事件只有共享 journal 一套权威,不装配旧 TaskStore;push notification 配置和 authenticated extended card RPC 返回 SDK 标准不支持/未配置错误。应用通过公开 executor 接口接入,不导入内部 handler/Store 装配类型。
457
+ - Context 的 flush 会报告本节点后台提取异常及失败 HaiResult;同轮并发调用共享一次报告,返回即确认该批,后续只观察新任务。save/reset(waitForMemoryTasks)/consolidate 传播失败并停止后续写入,pendingMemoryTasks=0 不代表成功,不自动重试未知副作用。
@@ -4,7 +4,9 @@
4
4
 
5
5
  ## 初始化与依赖
6
6
 
7
- 未初始化 reldb/vecdb 时,`ai.init()` 自动使用进程内临时 Store;两者均已初始化时使用持久化 DB Provider。需要跨进程持久化时必须使用 DB 或自定义 Provider。
7
+ 开发/显式测试未初始化 reldb/vecdb 时使用临时 Store;两者就绪时使用 DB Provider。`NODE_ENV` 或 `HAI_CORE_ENV=production` 要求 stateScope 为 shared,内置实现必须为 PostgreSQL/MySQL + 网络 vecdb;本地后端初始化失败,自定义 Provider 必须兑现存储时钟、CAS 与最终截止条件。
8
+
9
+ MySQL 首次初始化允许多个节点并发建索引:仅接受 mysql2 明确的 ER_DUP_KEYNAME/1061,并重新核对列及顺序、非唯一完整列、升序可见 BTREE 和无表达式;已有同名错误结构仍失败。账户需要建表和 INDEX 权限,元数据读取、权限或其他 DDL 错误使 ai.init 返回 CONFIGURATION_ERROR。返回前等待全部已启动 DDL 结束;MySQL DDL 不回滚,失败保留已创建结构,修复权限/结构后再重试。逐步检查初始化结果,失败不接收业务请求,并逆序关闭 AI、vecdb、reldb。
8
10
 
9
11
  | 能力 | 是否需要 `ai.init()` | 依赖 |
10
12
  | --- | --- | --- |
@@ -109,8 +111,16 @@ Embedding OpenAI 客户端按 `apiKey + baseURL + timeout` 缓存;同一 key
109
111
  Memory 后端通过 `memory.provider` 选择:
110
112
 
111
113
  - `native`(默认):HAI 原生引擎,复用 vecdb/reldb/LLM/Embedding。`extract` 采用 Mem0 式批量合并(ADD/UPDATE/DELETE/NONE + `category`);`maxEntriesPerObject`(单主体配额)/`maxEntriesGlobal`(全局上限)/`recencyDecay`/`embeddingEnabled`/`writebackRelatedTopK` 作用于此后端;淘汰按 `objectId` 分区。
112
- - `mem0`:直接使用 `mem0ai/oss` 的 `Memory` 引擎(嵌入式)。LLM/Embedder 从 `llm` 配置提取(OpenAI 兼容,场景模型 `extraction` / `embedding`);向量库从底层 vecdb 后端提取(`storeProvider.getVectorBackend()`),qdrant/pgvector 复用同一后端;无法映射 lancedb/chroma 等后端时**默认 fail-fast**(除非 `memory.allowEphemeralFallback: true` 才退回 mem0 in-memory);历史默认禁用。
113
- - 两者对外 API(extract/recall/injectMemories/add/update/get/remove/list/listPage/clear)完全一致;`objectId` 隔离不同主体的记忆,`scope`(key-value)用于业务作用域隔离(recall/list/listPage/clear 均严格过滤)。mem0 后端 `extract` 在框架层分类打分后以 `infer:false` 写入(保留 `hai_type`/`hai_importance`),`recall` 支持 `types`/`recencyWeight`,与 native 一致;mem0 后端 `update` 涉及 type/importance/metadata 时会重建记忆并重新分配 `id`。
114
+ - `mem0`:直接使用 `mem0ai/oss` 的 `Memory` 引擎(嵌入式)。LLM/Embedder 从 `llm` 配置提取(OpenAI 兼容,场景模型 `extraction` / `embedding`);向量库从底层 vecdb 后端提取(`storeProvider.getVectorBackend()`),qdrant 复用同一后端;当前适配仅映射带完整连接参数的 `qdrant`;生产环境(`NODE_ENV` 或 `HAI_CORE_ENV` 为 `production`)即使设置 `memory.allowEphemeralFallback: true` 也拒绝实际内存回退。临时回退仅限开发或明确的 `HAI_E2E=1` / `HAI_E2E_MOCK=1`;缺少后端同样不能在生产启动。历史默认禁用。
115
+ - 两者对外 API(extract/recall/injectMemories/add/update/get/remove/list/listPage/clear)完全一致;`objectId` 隔离不同主体的记忆,`scope`(key-value)用于业务作用域隔离(recall/list/listPage/clear 均严格过滤)。mem0 后端 `extract` 在框架层分类打分后以 `infer:false` 写入(保留 `hai_type`/`hai_importance`),`recall` 支持 `types`/`recencyWeight`,与 native 一致;Mem0 的 `update` 使用 SDK 原地修改文本与元数据,保持 `id` 和创建时间。SDK 查询使用 `filters.user_id`,写入和整主体删除使用 `userId`。SDK 无列表游标:`list` / `listPage` / 条件 `clear` 最多支持每主体 1000 条原始候选(含过期条目),多取一条探测完整性;超限返回 `MEMORY_LIST_INCOMPLETE`(906),不会伪造 total 或执行部分条件删除。候选完整时按 UTC 日期剔除过期项,再按类型/scope 过滤和分页;仅 `objectId` 的清理使用 SDK `deleteAll`,不受列表上限限制。公共空条件清理被拒绝;跨主体全清仅限受信管理员显式 `admin.clearAll({ confirm: true })`。
116
+
117
+ Mem0 3.3.1 的 Qdrant 适配仍依赖 `QdrantClient.search`;项目将 `@qdrant/js-client-rest` 精确锁定为受测的 `1.18.0`,AI 的可选 peer 也声明此版本。启动会检查该公开方法,缺失时明确配置失败;不要覆盖为已移除此方法的 1.19。
118
+
119
+ 适配器通过 SDK 公开的 `client` 配置注入独享客户端;Mem0 的 Qdrant 写入与删除均使用 `wait: true`,等待服务端应用完成后才继续写后读或分批清理。
120
+
121
+ Mem0 的 pgvector 组合在所有环境拒绝启动:当前 SDK 将 PostgreSQL 连接保存在私有对象中,未提供公开关闭入口,框架无法履行资源清理契约。需要 pgvector 时选择 `memory.provider: native`;该原生后端保持支持。`disableHistory: true` 使用 SDK 无资源的历史占位实现,不创建历史 SQLite 文件。工厂通过公开的一条有界只读查询等待实际后端 ready 后才发布成功。
122
+
123
+ `add` / `extract` 的 SDK 回执不含完整业务字段,框架写后读取权威记录再返回;并发提取等待全部在途写入结束。若写入或写后读失败,`error.ext.requiresEffectReconciliation=true`,`memoryIds` 保存已知写入 ID(回执丢失时可能为空)。应按主体核对实际记忆后决定恢复,不能盲目重做 add/extract;框架不自动重放。
114
124
 
115
125
  ## Retrieval / RAG
116
126
 
@@ -144,16 +154,31 @@ const result = await ai.rag.query('核心架构是什么?', {
144
154
  | `findByEntity(entityName, options?)` | 实体关联文档 |
145
155
  | `listEntities(options?)` / `listDocuments(options?)` | 列表 |
146
156
  | `removeDocument(documentId, options?)` | 删除文档和关联 |
157
+ | `repair({ collection?, offset?, limit? })` | 有界恢复过期暂存与失败清理,删除集合后也可调用 |
158
+
159
+ 入库链路:clean/chunk → embedding/完整实体提取 → 持久暂存清单 → vecdb upsert → 单文档 CAS 发布完整正文、实体和元数据。每次产生唯一 revision/chunk ID,相同文档并发重建只允许一个 writer;失败保留旧版,短文档重建不显示旧尾块。向量检索只保留当前发布版,切换期间重试,持续冲突或候选扩展超限返回错误。
160
+
161
+ `ingest` 成功返回 `revision`、`cleanupPending`。后者为 true 表示完整新版已经发布但旧向量删除失败,须检查并安排 repair,不能当作全部清理完成。`setup` 自动扫描前 100 份清单;应用定期分页 repair(offset≥0,limit 1–1000,默认100),根据 processed/total 覆盖全部页。存储端时钟的 5 分钟暂存过期后旧 owner 不能发布;废弃写入的永久补偿墓碑用于反复清理迟到外部写入,不能随日志删除。SQL 与 vecdb 无跨库事务,故障/重启后的清理依赖持久记录及重复扫描。
162
+
163
+ 删除先撤销可见性,清理失败返回错误并保留补偿信息。集合删除保留物理向量集合与代际墓碑,重新 setup 不会恢复旧代数据。默认 Store 的正文/实体来自同一已发布快照;自定义 KnowledgeStore 必须实现相同发布、删除和补偿契约。Qdrant 写入/删除等待应用,候选上限使用精确计数;LanceDB 使用持久主键原子 merge。
164
+
165
+ 使用 LanceDB 时,集合维度与 metric 随 Arrow schema 持久化;连接 metric 只影响新集合。重启后复用同一持久目录,embedding 维数改变时重建匹配集合;多个节点各自的本地路径不会自动共享,分布式 Knowledge 推荐服务端向量后端。
147
166
 
148
- 入库链路:parse/clean → chunk → embedding → vecdb upsert → reldb metadata/entity index。
167
+ Knowledge `retrieve/ask` 与默认 DB Store 的 filter 透传向量 Provider,校验/查询失败必须传播(Knowledge 为 KNOWLEDGE_RETRIEVE_FAILED,ask 为 RAG_FAILED),不能当作空成功结果。LanceDB 仅支持顶层字面量键的 string/有限 number/boolean/null AND 匹配,例如 `{ tenant: 'acme', active: true }`;null 不匹配缺失字段,对象/数组/undefined 返回错误。过滤在向量 topK 前执行;其它后端的 null/复杂值语义须独立核对。LanceDB 原生 JSON 字段保留嵌套 JSON,但旧 Utf8 metadata 集合必须重建后才能写入或过滤。
149
168
 
150
169
  ## Context
151
170
 
171
+ `scope` 必填;共享 scope 用版本化聚合记录保存消息、摘要、owner、轮次和 pending。所有消息、对外 delta 与提交即时持久化。同步 getters 仅返回本管理器最近一次成功读写的独立快照,读取其他节点修改前调用 `await manager.reload()` 或 `restoreManager()`。`save()` 等待本节点记忆提取、刷新快照并同步目录,不能覆盖远端版本。`markTurnSpeaking` 为异步持久化方法。
172
+
173
+ 共享单活动生成租约为存储端时钟30秒,每10秒续租,最终 CAS 同时检查版本、owner 和截止时间。过期生成仅 interrupted,不自动重放 LLM/工具/记忆;manual pending 持久保存,可跨节点/重启按 turnId 提交。相同终态与真实文本重复提交幂等,内容冲突失败。queue 可取消轮询,不保证FIFO。reset 更换共享纪元,删除保留永久墓碑且 scope 不复用。外部 LLM/工具/本地后台记忆不与上下文跨系统原子提交;崩溃后的未知副作用必须由应用核对。
174
+
175
+ 自定义 `AIRelStore` 必须提供版本化读取、原子 CAS 和存储端 `currentTime()`;普通 save/saveMany 也推进版本。CAS 缺失版本只允许创建,notAfter 在最终持久化时严格检查,存储故障抛错不能当空数据;业务 HaiResult 边界负责转失败。默认 DB schema 含 version 列,不自动迁移旧表;SQLite 仅用于开发/测试,生产必须 PostgreSQL/MySQL 与共享向量服务。
176
+
152
177
  `ai.context.createManager(options)` 返回有状态管理器,适合多轮对话。
153
178
 
154
179
  常用选项:
155
180
 
156
- - `scope: { objectId, sessionId }`
181
+ - `scope: { objectId, sessionId }`(必填)
157
182
  - `systemPrompt`
158
183
  - `compress: { auto, strategy, maxTokens, preserveLastN }`
159
184
  - `memory: { enable, enableExtract, scope, types, minImportance, topK, position, extractionModel, extractionSystemPrompt }`(scope/types 等完整透传给 Memory 的 recall/extract)
@@ -162,6 +187,8 @@ const result = await ai.rag.query('核心架构是什么?', {
162
187
 
163
188
  常用方法:`addMessage`、`getMessages`、`getTokenUsage`、`chat`、`chatStream`、`save`、`reset`、`flush`(等待后台记忆提取完成,`save` 前自动调用)、`pendingMemoryTasks`(挂起提取任务数)。`chat/chatStream` 支持 `signal` 取消。
164
189
 
190
+ flush 对本节点后台提取的异常和失败 HaiResult 返回 MEMORY_EXTRACT_FAILED(含失败数量);pendingMemoryTasks=0 不保证成功。同轮并发观察者共享结果,返回即确认本批报告,后续只观察新任务,不自动重放未知副作用。save、reset({ waitForMemoryTasks: true })、consolidate 会传播失败并停止后续写入,调用方必须检查 success。
191
+
165
192
  ## File / Rerank / Reasoning
166
193
 
167
194
  - `ai.file.parse({ content, filename, options })`:text/html/pdf/docx/ocr 解析。
@@ -171,6 +198,8 @@ const result = await ai.rag.query('核心架构是什么?', {
171
198
 
172
199
  ## Audio
173
200
 
201
+ IndexTTS 的 Node HTTP 请求使用同版本 Undici fetch/Agent/FormData;模型 timeout 覆盖响应头与正文等待,主动取消为 AUDIO_CANCELLED、超时为 AUDIO_TIMEOUT,结束后释放独立连接。完整调用返回 HaiResult,流式降级保留迭代抛错。
202
+
174
203
  | 方法 | 返回 | 说明 |
175
204
  | --- | --- | --- |
176
205
  | `transcribe(request)` | `Promise<HaiResult<TranscriptionResult>>` | 完整音频 → 完整文本 |
@@ -196,6 +225,9 @@ const result = await ai.rag.query('核心架构是什么?', {
196
225
  - 请求:`GenerateImageRequest { prompt, model?, size?: { width, height }, referenceImages?: ReferenceImage[], signal? }`;`ReferenceImage` 为 `{ data: Uint8Array, mimeType: string }`。
197
226
  - 返回:`GenerateImageResult { images: Array<{ data: Uint8Array, mimeType, width?, height? }> }`。
198
227
  - 模型配置:`image.models: [{ id, provider: 'openai'|'google'|'qwen'|'seedream'|'pollinations', model, apiKey?, baseUrl?, workspaceId?, timeout? }]`;`image.generateModel` 选择默认模型别名。
228
+ - `image.maxImageBytes` 默认20971520、最大104857600,限制单张输出/参考图;`image.maxImages` 默认4、范围1–16。JSON正文最多max(64KiB,两倍图片上限),错误正文64KiB,实际解压后字节受限,URL串行下载。环境入口HAI_AI_IMAGE_MAXIMAGEBYTES / HAI_AI_IMAGE_MAXIMAGES。
229
+ - 临时URL仅允许无凭证、默认443公网HTTPS,实际socket DNS候选全部校验并固定使用,私网/回环/链路本地/映射/转换/保留地址拒绝;最多3次重定向且每跳校验,不传认证头/Cookie。baseUrl为可信运维配置,厂商API不跟随重定向。模型timeout默认120000ms覆盖整次生成/下载,signal可取消。
230
+ - 输出PNG/JPEG/GIF/WebP须MIME与魔数相符,SVG拒绝;宽高仅为请求/厂商提示,不做完整解码。地址拒绝/超限/超时分别为IMAGE_DOWNLOAD_BLOCKED/IMAGE_TOO_LARGE/IMAGE_TIMEOUT,调用方检查success后再使用。
199
231
  - 凭据优先使用模型项 `apiKey`,否则回退 `HAI_AI_IMAGE_<PROVIDER>_APIKEY` 或厂商标准环境变量。
200
232
  - 厂商差异由内部 Provider 消化:OpenAI/Pollinations 的 multipart edits、Google `inlineData`、Qwen/Seedream 的 Data URL 参考图,以及各家 Base64、临时 URL 或二进制响应均归一为图片字节。
201
233
  - Google 只接受其文档列出的宽高比;Qwen/Seedream 还可能对总像素与边长有限制。业务应提供厂商支持的尺寸预设,不要假设任意像素组合都可用。
@@ -216,7 +248,7 @@ const result = await ai.rag.query('核心架构是什么?', {
216
248
 
217
249
  A2A 服务端 SDK handler 在注册 executor 时延迟创建;远端客户端调用不依赖服务端配置。getAgentCard 返回完整 A2A 0.3 Card(MIME、streaming、安全方案);Kit 默认发现路径为 `/.well-known/agent-card.json`。handleRequest 在未初始化时也返回 JSON-RPC 错误;可信 context.agentId 映射 SDK user.userName。Kit 配置 authenticate 后空身份返回 401;不声明 push notifications、gRPC、REST 支持。
218
250
 
219
- A2A 的任务表和消息表在 `ai.init()` 阶段初始化。任务按可信 `agentId` 隔离读取(含获取、取消和继续消息);匿名请求共享匿名作用域,需隔离的部署必须开启认证。`listMessages` 是服务端管理接口,支持 `callerId`、`contextId`、`since` 过滤,不能直接向未授权客户端暴露。旧版任务记录没有可靠主体信息,升级后不会自动推断归属或删除数据,需由应用审核后迁移 `object_id` 为 `user:<agentId>`(匿名为 `anonymous`)。旧消息缺少主体/上下文字段时不会匹配相应过滤条件。
251
+ A2A journal、context 归属和消息表在 `ai.init()` 阶段初始化;可信 agentId 是身份来源,匿名共用主体,生产须认证。Task 与事件一次 CAS 发布,支持任意节点订阅、真实 owner 取消和 input-required 跨节点续接。同 message ID/内容重试复用同一轮,内容改变或旧轮重试拒绝;取消未确认时同一 Task 拒绝新消息,防止迟到回调影响新一代;失租、绝对期限或 working 状态退出标记 failed/interrupted 与 metadata.haiExecution.requiresEffectReconciliation=true,不自动重放未知副作用。a2a.execution 默认 leaseMs=30000、pollIntervalMs=250、maxExecutionMs=300000;单消息1MiB、journal8MiB、每轮2048事件、每Task256回执、32个reference tasks。listMessages 是服务端管理接口,callerId/contextId/since 不能替代调用授权。外部工具须使用稳定业务幂等键并核对未知结果,SDK异常不向用户暴露驱动凭据。
220
252
 
221
253
  `callRemoteAgent` 只接受 HTTP(S) 且拒绝 URL 内嵌凭据。该校验不是完整 SSRF 防护:remote URL 若来自外部输入,应用必须先按 origin 白名单过滤,并通过出口代理限制 DNS 重绑定、重定向到私网与云元数据地址。
222
254
 
@@ -228,7 +260,7 @@ A2A 的任务表和消息表在 `ai.init()` 阶段初始化。任务按可信 `a
228
260
  | `hai:ai:010-012` | 初始化:`NOT_INITIALIZED` / `CONFIGURATION_ERROR` / `INIT_IN_PROGRESS` |
229
261
  | `hai:ai:020-033` | Rerank / File |
230
262
  | `hai:ai:050-059` | Audio:`AUDIO_INVALID_REQUEST` / `MODEL_NOT_FOUND` / `PROVIDER_NOT_FOUND` / `UNSUPPORTED_INPUT` / `UPSTREAM_ERROR` / `PROTOCOL_ERROR` / `CONNECTION_FAILED` / `TIMEOUT` / `INPUT_TOO_LARGE` / `CANCELLED` |
231
- | `hai:ai:060-065` | Image:`IMAGE_INVALID_REQUEST` / `MODEL_NOT_FOUND` / `PROVIDER_NOT_FOUND` / `UPSTREAM_ERROR` / `PROTOCOL_ERROR` / `CANCELLED` |
263
+ | `hai:ai:060-068` | Image:`IMAGE_INVALID_REQUEST` / `MODEL_NOT_FOUND` / `PROVIDER_NOT_FOUND` / `UPSTREAM_ERROR` / `PROTOCOL_ERROR` / `CANCELLED` / `DOWNLOAD_BLOCKED` / `TOO_LARGE` / `TIMEOUT` |
232
264
  | `hai:ai:100-107` | LLM / 历史记录 |
233
265
  | `hai:ai:200-204` | MCP |
234
266
  | `hai:ai:300-302` | Embedding |
@@ -236,17 +268,25 @@ A2A 的任务表和消息表在 `ai.init()` 阶段初始化。任务按可信 `a
236
268
  | `hai:ai:500-502` | Reasoning |
237
269
  | `hai:ai:600-701` | Retrieval / RAG |
238
270
  | `hai:ai:800-805` | Knowledge |
239
- | `hai:ai:900-905` | Memory |
271
+ | `hai:ai:900-906` | Memory |
240
272
  | `hai:ai:950-971` | Context / Store / Session |
241
273
  | `hai:ai:980-984` | A2A |
242
274
 
243
275
  ## 浏览器端
244
276
 
245
- 浏览器端通过 `@h-ai/api-client` 或自定义 endpoint 访问服务端 AI 能力。`ai.stream` 可在浏览器直接用于解析服务端流式 chunk。
277
+ 浏览器端通过 `@h-ai/api-client` 或自定义 endpoint 访问服务端 AI 能力。使用 serv 默认 AI feature 时,objectId 从可信认证 session 绑定,客户端可省略;其它主体默认拒绝,跨用户管理必须由服务端显式 authorizeObject。聊天/消息写入、历史、会话和记忆均按该主体隔离,sessionId 是主体内的会话标识。浏览器入口不提供 Node 端 `ai` 实例或 `ai.stream`;流式与 A2A 使用应用实际声明的独立契约。
278
+
279
+ 默认 `chats.createCompletion`(`POST /ai/chats/completions`)只返回非流式 `HaiResult<AiChatCompletionData>`,未声明字段(含 `stream: true/false`)在 HTTP 边界拒绝。function `tools` 保留完整嵌套 JSON Schema 与 `strict`,`tool_choice` 支持 auto/none/required/本次定义的函数名。消息按角色校验并保留 name、用户图片、助手 tool_calls;工具结果必须带非空 tool_call_id。应用自行授权执行工具后,将助手调用与对应结果回传。需要流式时定义与 `ai.llm.chatStream()` 匹配的独立 endpoint,不把 stream 填入默认补全请求。
246
280
 
247
281
  ```ts
248
282
  const response = await apiClient.ai.chats.createCompletion({ messages })
249
- const memories = await apiClient.ai.memories.recall({ query: '偏好', objectId: 'user-001', topK: 5 })
283
+ const memories = await apiClient.ai.memories.recall({ query: '偏好', topK: 5 })
250
284
  ```
251
285
 
252
- 语音:`@h-ai/ai/client` 的 `createAIClient({ api, audio: { url, getTicket } })` 提供 `client.audio.transcribe/transcribeStream/synthesize/synthesizeStream`。`getTicket(request)` 接收本次操作摘要(`operation` / `model`),应通过已登录 HTTP 请求签发与本次操作严格绑定的短期、一次性 Audio ticket(推荐 `iam.ticket.issue({ grant })`);IAM access token 不进入 WebSocket URL。
286
+ 语音:`@h-ai/ai/client` 的 `createAudioClient({ url, getTicket })` 提供 `client.transcribe/transcribeStream/synthesize/synthesizeStream`。`getTicket(request)` 接收本次操作摘要(`operation` / `model`),应通过已登录 HTTP 请求签发与本次操作严格绑定的短期、一次性 Audio ticket(推荐 `iam.ticket.issue({ grant })`);IAM access token 不进入 WebSocket URL。
287
+
288
+ Knowledge 分块直接遵循 datapipe 的单位与硬上限:word 为词,其他为 Unicode code point,保留清洗后文本的分隔符;AI 默认 chunkOptions 仍为 markdown/maxSize=1500/overlap=200,覆盖较小 maxSize 时须同时保证继承的 overlap 小于它。分块配置/表达式失败返回 KNOWLEDGE_INGEST_FAILED,embedding 与版本发布均不会执行,不退回整篇文本绕过预算。
289
+
290
+ JSDoc 公开示例使用真实包入口,内部工厂示例标记“模块内部上下文”;应用使用所安装版本的公开类型,并通过本应用已有的类型检查验证使用示例。调用示例须保留 HaiResult 成功检查、受信主体范围、共享 CAS 版本与存储端期限,不将后台 flush 失败或未知外部效果当作成功重试。
291
+
292
+ SSE:Node 的 ai.stream 解码器支持跨片 LF/CRLF/CR、多行 data,只去冒号后一个空格;无 data 块不派发,空或缺省事件名按 message,id/retry 继承最近有效值,reset 清空连接状态,EOF 不补空行。encodeSSE 允许合法 data 换行并规范化,id/event 换行、id 的 NUL、非负安全整数以外的 retry 显式抛 TypeError/RangeError。真实 Anthropic/MiMo 使用同一内部 UTF-8 reader,退出/取消/协议结束/错误均取消正文并释放 reader;无自动重连/副作用重放。Anthropic 总 timeout 覆盖正文,MiMo 保留每请求独立 timeout。
@@ -32,3 +32,5 @@ description: "接入或排查 @h-ai/api-client typed API、登录 Token、自动
32
32
  - [核心 API](reference.md#核心-api)
33
33
  - [常见模式](reference.md#常见模式)
34
34
  - [配置驱动认证页面](reference.md#配置驱动认证页面)
35
+
36
+ 普通请求与 Token 刷新自动将当前 `core.i18n` 语言发送为 `Accept-Language`;切换语言时同步 core。`headers` 中显式配置的同名头优先。