@huyooo/ai-chat-core 0.2.45 → 0.3.2

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 (247) hide show
  1. package/dist/adapter/index.d.ts +11 -0
  2. package/dist/adapter/index.d.ts.map +1 -0
  3. package/dist/adapter/model-adapter.d.ts +25 -0
  4. package/dist/adapter/model-adapter.d.ts.map +1 -0
  5. package/dist/adapter/model-options.d.ts +53 -0
  6. package/dist/adapter/model-options.d.ts.map +1 -0
  7. package/dist/adapter/types.d.ts +28 -0
  8. package/dist/adapter/types.d.ts.map +1 -0
  9. package/dist/chat-runtime.d.ts +96 -0
  10. package/dist/chat-runtime.d.ts.map +1 -0
  11. package/dist/constants.d.ts +12 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/events.d.ts +605 -1
  14. package/dist/events.d.ts.map +1 -0
  15. package/dist/events.js +1 -1
  16. package/dist/extension/index.d.ts +9 -0
  17. package/dist/extension/index.d.ts.map +1 -0
  18. package/dist/extension/types.d.ts +46 -0
  19. package/dist/extension/types.d.ts.map +1 -0
  20. package/dist/families/index.d.ts +11 -0
  21. package/dist/families/index.d.ts.map +1 -0
  22. package/dist/families/presets.d.ts +31 -0
  23. package/dist/families/presets.d.ts.map +1 -0
  24. package/dist/families/resolver.d.ts +11 -0
  25. package/dist/families/resolver.d.ts.map +1 -0
  26. package/dist/families/types.d.ts +29 -0
  27. package/dist/families/types.d.ts.map +1 -0
  28. package/dist/governance/command-safety.d.ts +34 -0
  29. package/dist/governance/command-safety.d.ts.map +1 -0
  30. package/dist/governance/governance.d.ts +19 -0
  31. package/dist/governance/governance.d.ts.map +1 -0
  32. package/dist/governance/index.d.ts +12 -0
  33. package/dist/governance/index.d.ts.map +1 -0
  34. package/dist/governance/types.d.ts +29 -0
  35. package/dist/governance/types.d.ts.map +1 -0
  36. package/dist/index.d.ts +72 -804
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +51 -1
  39. package/dist/internal/management-args.d.ts +13 -0
  40. package/dist/internal/management-args.d.ts.map +1 -0
  41. package/dist/internal/management-results.d.ts +21 -0
  42. package/dist/internal/management-results.d.ts.map +1 -0
  43. package/dist/llm-config.d.ts +108 -0
  44. package/dist/llm-config.d.ts.map +1 -0
  45. package/dist/logger/core.d.ts +31 -0
  46. package/dist/logger/core.d.ts.map +1 -0
  47. package/dist/logger/index.d.ts +9 -0
  48. package/dist/logger/index.d.ts.map +1 -0
  49. package/dist/orchestrator/compression-handler.d.ts +29 -0
  50. package/dist/orchestrator/compression-handler.d.ts.map +1 -0
  51. package/dist/orchestrator/context-compressor.d.ts +51 -0
  52. package/dist/orchestrator/context-compressor.d.ts.map +1 -0
  53. package/dist/orchestrator/context-summarizer.d.ts +41 -0
  54. package/dist/orchestrator/context-summarizer.d.ts.map +1 -0
  55. package/dist/orchestrator/index.d.ts +12 -0
  56. package/dist/orchestrator/index.d.ts.map +1 -0
  57. package/dist/orchestrator/orchestrator.d.ts +46 -0
  58. package/dist/orchestrator/orchestrator.d.ts.map +1 -0
  59. package/dist/orchestrator/types.d.ts +58 -0
  60. package/dist/orchestrator/types.d.ts.map +1 -0
  61. package/dist/parts/index.d.ts +13 -0
  62. package/dist/parts/index.d.ts.map +1 -0
  63. package/dist/parts/registry.d.ts +11 -0
  64. package/dist/parts/registry.d.ts.map +1 -0
  65. package/dist/parts/summaries.d.ts +9 -0
  66. package/dist/parts/summaries.d.ts.map +1 -0
  67. package/dist/parts/types.d.ts +61 -0
  68. package/dist/parts/types.d.ts.map +1 -0
  69. package/dist/platform.d.ts +17 -0
  70. package/dist/platform.d.ts.map +1 -0
  71. package/dist/platform.js +1 -0
  72. package/dist/protocols/anthropic.d.ts +20 -0
  73. package/dist/protocols/anthropic.d.ts.map +1 -0
  74. package/dist/protocols/ark.d.ts +36 -0
  75. package/dist/protocols/ark.d.ts.map +1 -0
  76. package/dist/protocols/deepseek.d.ts +24 -0
  77. package/dist/protocols/deepseek.d.ts.map +1 -0
  78. package/dist/protocols/error-utils.d.ts +14 -0
  79. package/dist/protocols/error-utils.d.ts.map +1 -0
  80. package/dist/protocols/gemini.d.ts +24 -0
  81. package/dist/protocols/gemini.d.ts.map +1 -0
  82. package/dist/protocols/glm.d.ts +20 -0
  83. package/dist/protocols/glm.d.ts.map +1 -0
  84. package/dist/protocols/grok.d.ts +20 -0
  85. package/dist/protocols/grok.d.ts.map +1 -0
  86. package/dist/protocols/index.d.ts +31 -0
  87. package/dist/protocols/index.d.ts.map +1 -0
  88. package/dist/protocols/minimax.d.ts +38 -0
  89. package/dist/protocols/minimax.d.ts.map +1 -0
  90. package/dist/protocols/moonshot.d.ts +20 -0
  91. package/dist/protocols/moonshot.d.ts.map +1 -0
  92. package/dist/protocols/openai-sse.d.ts +33 -0
  93. package/dist/protocols/openai-sse.d.ts.map +1 -0
  94. package/dist/protocols/openai.d.ts +19 -0
  95. package/dist/protocols/openai.d.ts.map +1 -0
  96. package/dist/protocols/qwen.d.ts +26 -0
  97. package/dist/protocols/qwen.d.ts.map +1 -0
  98. package/dist/protocols/responses-sse.d.ts +30 -0
  99. package/dist/protocols/responses-sse.d.ts.map +1 -0
  100. package/dist/protocols/sse-reader.d.ts +23 -0
  101. package/dist/protocols/sse-reader.d.ts.map +1 -0
  102. package/dist/protocols/tool-arguments.d.ts +8 -0
  103. package/dist/protocols/tool-arguments.d.ts.map +1 -0
  104. package/dist/protocols/types.d.ts +148 -0
  105. package/dist/protocols/types.d.ts.map +1 -0
  106. package/dist/protocols/vercel-gateway.d.ts +15 -0
  107. package/dist/protocols/vercel-gateway.d.ts.map +1 -0
  108. package/dist/runtime.d.ts +151 -0
  109. package/dist/runtime.d.ts.map +1 -0
  110. package/dist/runtime.js +1 -0
  111. package/dist/skills/index.d.ts +14 -0
  112. package/dist/skills/index.d.ts.map +1 -0
  113. package/dist/skills/management/admin.d.ts +10 -0
  114. package/dist/skills/management/admin.d.ts.map +1 -0
  115. package/dist/skills/management/index.d.ts +11 -0
  116. package/dist/skills/management/index.d.ts.map +1 -0
  117. package/dist/skills/management/inputs.d.ts +44 -0
  118. package/dist/skills/management/inputs.d.ts.map +1 -0
  119. package/dist/skills/management/operations.d.ts +78 -0
  120. package/dist/skills/management/operations.d.ts.map +1 -0
  121. package/dist/skills/management/types.d.ts +70 -0
  122. package/dist/skills/management/types.d.ts.map +1 -0
  123. package/dist/skills/registry.d.ts +37 -0
  124. package/dist/skills/registry.d.ts.map +1 -0
  125. package/dist/skills/summaries.d.ts +9 -0
  126. package/dist/skills/summaries.d.ts.map +1 -0
  127. package/dist/skills/types.d.ts +61 -0
  128. package/dist/skills/types.d.ts.map +1 -0
  129. package/dist/test-utils/mock-sse.d.ts +13 -0
  130. package/dist/test-utils/mock-sse.d.ts.map +1 -0
  131. package/dist/tool-manager/define-tool.d.ts +35 -0
  132. package/dist/tool-manager/define-tool.d.ts.map +1 -0
  133. package/dist/tool-manager/formats.d.ts +46 -0
  134. package/dist/tool-manager/formats.d.ts.map +1 -0
  135. package/dist/tool-manager/identity.d.ts +18 -0
  136. package/dist/tool-manager/identity.d.ts.map +1 -0
  137. package/dist/tool-manager/in-process-provider.d.ts +15 -0
  138. package/dist/tool-manager/in-process-provider.d.ts.map +1 -0
  139. package/dist/tool-manager/index.d.ts +18 -0
  140. package/dist/tool-manager/index.d.ts.map +1 -0
  141. package/dist/tool-manager/manager.d.ts +18 -0
  142. package/dist/tool-manager/manager.d.ts.map +1 -0
  143. package/dist/tool-manager/mcp-provider.d.ts +21 -0
  144. package/dist/tool-manager/mcp-provider.d.ts.map +1 -0
  145. package/dist/tool-manager/summaries.d.ts +39 -0
  146. package/dist/tool-manager/summaries.d.ts.map +1 -0
  147. package/dist/tool-manager/types.d.ts +314 -0
  148. package/dist/tool-manager/types.d.ts.map +1 -0
  149. package/dist/types.d.ts +663 -0
  150. package/dist/types.d.ts.map +1 -0
  151. package/package.json +26 -15
  152. package/src/adapter/index.ts +25 -0
  153. package/src/adapter/model-adapter.ts +196 -0
  154. package/src/adapter/model-options.ts +143 -0
  155. package/src/adapter/types.ts +41 -0
  156. package/src/chat-runtime.ts +515 -0
  157. package/src/constants.ts +9 -102
  158. package/src/events.ts +364 -150
  159. package/src/extension/index.ts +24 -0
  160. package/src/extension/types.ts +49 -0
  161. package/src/families/index.ts +28 -0
  162. package/src/families/presets.ts +124 -0
  163. package/src/families/resolver.ts +22 -0
  164. package/src/families/types.ts +55 -0
  165. package/src/governance/command-safety.ts +224 -0
  166. package/src/governance/governance.ts +125 -0
  167. package/src/governance/index.ts +38 -0
  168. package/src/governance/types.ts +44 -0
  169. package/src/index.ts +250 -145
  170. package/src/internal/management-args.ts +39 -0
  171. package/src/internal/management-results.ts +60 -0
  172. package/src/llm-config.ts +137 -0
  173. package/src/logger/core.ts +96 -0
  174. package/src/logger/index.ts +8 -0
  175. package/src/orchestrator/compression-handler.ts +137 -0
  176. package/src/{providers → orchestrator}/context-compressor.ts +79 -47
  177. package/src/orchestrator/context-summarizer.ts +123 -0
  178. package/src/orchestrator/index.ts +20 -0
  179. package/src/orchestrator/orchestrator.ts +1002 -0
  180. package/src/orchestrator/types.ts +70 -0
  181. package/src/parts/index.ts +20 -0
  182. package/src/parts/registry.ts +95 -0
  183. package/src/parts/summaries.ts +40 -0
  184. package/src/parts/types.ts +63 -0
  185. package/src/platform.ts +73 -0
  186. package/src/protocols/anthropic.ts +377 -0
  187. package/src/protocols/ark.ts +300 -0
  188. package/src/protocols/deepseek.ts +192 -0
  189. package/src/{providers/protocols → protocols}/error-utils.ts +17 -20
  190. package/src/protocols/gemini.ts +352 -0
  191. package/src/protocols/glm.ts +212 -0
  192. package/src/protocols/grok.ts +98 -0
  193. package/src/protocols/index.ts +48 -0
  194. package/src/protocols/minimax.ts +308 -0
  195. package/src/protocols/moonshot.ts +186 -0
  196. package/src/protocols/openai-sse.ts +156 -0
  197. package/src/protocols/openai.ts +97 -0
  198. package/src/protocols/qwen.ts +358 -0
  199. package/src/protocols/responses-sse.ts +224 -0
  200. package/src/protocols/sse-reader.ts +54 -0
  201. package/src/protocols/tool-arguments.ts +32 -0
  202. package/src/{providers/protocols → protocols}/types.ts +46 -37
  203. package/src/protocols/vercel-gateway.ts +391 -0
  204. package/src/runtime.ts +167 -0
  205. package/src/skills/index.ts +29 -0
  206. package/src/skills/management/admin.ts +170 -0
  207. package/src/skills/management/index.ts +27 -0
  208. package/src/skills/management/inputs.ts +79 -0
  209. package/src/skills/management/operations.ts +256 -0
  210. package/src/skills/management/types.ts +57 -0
  211. package/src/skills/registry.ts +120 -0
  212. package/src/skills/summaries.ts +48 -0
  213. package/src/skills/types.ts +65 -0
  214. package/src/test-utils/mock-sse.ts +3 -3
  215. package/src/tool-manager/define-tool.ts +201 -0
  216. package/src/tool-manager/formats.ts +146 -0
  217. package/src/tool-manager/identity.ts +80 -0
  218. package/src/tool-manager/in-process-provider.ts +164 -0
  219. package/src/tool-manager/index.ts +63 -0
  220. package/src/tool-manager/manager.ts +562 -0
  221. package/src/tool-manager/mcp-provider.ts +509 -0
  222. package/src/tool-manager/summaries.ts +136 -0
  223. package/src/tool-manager/types.ts +389 -0
  224. package/src/types.ts +750 -191
  225. package/dist/events-CU5D5ray.d.ts +0 -1128
  226. package/src/agent.ts +0 -409
  227. package/src/internal/update-plan.ts +0 -2
  228. package/src/internal/web-search.ts +0 -77
  229. package/src/mcp/client-manager.ts +0 -302
  230. package/src/mcp/index.ts +0 -2
  231. package/src/mcp/types.ts +0 -43
  232. package/src/providers/context-summarizer.ts +0 -70
  233. package/src/providers/index.ts +0 -125
  234. package/src/providers/model-registry.ts +0 -466
  235. package/src/providers/orchestrator.ts +0 -839
  236. package/src/providers/protocols/anthropic.ts +0 -406
  237. package/src/providers/protocols/ark.ts +0 -362
  238. package/src/providers/protocols/deepseek.ts +0 -344
  239. package/src/providers/protocols/gemini.ts +0 -350
  240. package/src/providers/protocols/index.ts +0 -36
  241. package/src/providers/protocols/openai.ts +0 -420
  242. package/src/providers/protocols/qwen.ts +0 -315
  243. package/src/providers/types.ts +0 -264
  244. package/src/providers/unified-adapter.ts +0 -367
  245. package/src/router.ts +0 -72
  246. package/src/tools.ts +0 -162
  247. package/src/utils.ts +0 -86
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huyooo/ai-chat-core",
3
- "version": "0.2.45",
3
+ "version": "0.3.2",
4
4
  "description": "AI Chat Core - HybridAgent with Doubao + Gemini",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,10 +17,20 @@
17
17
  "import": "./dist/events.js",
18
18
  "default": "./dist/events.js"
19
19
  },
20
- "./mcp": {
21
- "types": "./dist/mcp/index.d.ts",
22
- "import": "./dist/mcp/index.js",
23
- "default": "./dist/mcp/index.js"
20
+ "./tool-manager": {
21
+ "types": "./dist/tool-manager/index.d.ts",
22
+ "import": "./dist/tool-manager/index.js",
23
+ "default": "./dist/tool-manager/index.js"
24
+ },
25
+ "./runtime": {
26
+ "types": "./dist/runtime.d.ts",
27
+ "import": "./dist/runtime.js",
28
+ "default": "./dist/runtime.js"
29
+ },
30
+ "./platform": {
31
+ "types": "./dist/platform.d.ts",
32
+ "import": "./dist/platform.js",
33
+ "default": "./dist/platform.js"
24
34
  }
25
35
  },
26
36
  "files": [
@@ -28,25 +38,26 @@
28
38
  "src"
29
39
  ],
30
40
  "scripts": {
31
- "build": "tsup",
32
- "dev": "tsup --watch",
41
+ "build": "npm run clean && npm run build:js && npm run build:types",
42
+ "build:js": "tsdown",
43
+ "build:types": "tsc -b tsconfig.json --emitDeclarationOnly",
44
+ "dev": "tsdown --watch",
33
45
  "test": "vitest run",
46
+ "test:live": "vitest run --config vitest.live.config.ts",
47
+ "test:all": "npm run test && npm run test:live",
34
48
  "test:watch": "vitest",
35
49
  "typecheck": "tsc --noEmit",
36
- "clean": "rm -rf dist"
50
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
37
51
  },
38
52
  "dependencies": {
39
- "@ai-sdk/anthropic": "^3.0.1",
40
- "@ai-sdk/openai": "^1.0.0",
41
- "@google/genai": "^1.0.0",
42
53
  "@modelcontextprotocol/sdk": "^1.26.0",
43
- "ai": "^6.0.3",
44
- "openai": "^4.0.0",
45
- "zod": "^3.24.0"
54
+ "@sinclair/typebox": "^0.34.48",
55
+ "pino": "^10.3.1",
56
+ "pino-pretty": "^13.1.3"
46
57
  },
47
58
  "devDependencies": {
48
59
  "@types/node": "^22.0.0",
49
- "tsup": "^8.0.0",
60
+ "tsdown": "^0.21.0",
50
61
  "typescript": "^5.0.0",
51
62
  "vitest": "^4.0.18"
52
63
  },
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Adapter 模块导出(barrel)
3
+ *
4
+ * - ModelAdapter:按 LLMConfig 路由协议、流式消费 RawEvent
5
+ * - model-options:从前端 LLMConfig 构建模型列表与上下文展示配置
6
+ */
7
+
8
+ export { ModelAdapter } from './model-adapter';
9
+ export {
10
+ buildModelOptions,
11
+ getModelContextConfigFromLLM,
12
+ formatContextWindow,
13
+ formatPricing,
14
+ } from './model-options';
15
+
16
+ export type {
17
+ ModelOption,
18
+ ModelContextConfig,
19
+ } from './model-options';
20
+
21
+ export type {
22
+ ProviderAdapter,
23
+ AdapterConfig,
24
+ StreamOnceOptions,
25
+ } from './types';
@@ -0,0 +1,196 @@
1
+ /**
2
+ * ModelAdapter - 模型适配器(Model 中心)
3
+ *
4
+ * 直接使用 LLMConfig.models 的 ModelRoute 链,按顺序尝试,支持降级。
5
+ */
6
+
7
+ import type {
8
+ Protocol,
9
+ ProtocolConfig,
10
+ ProtocolMessage,
11
+ ProtocolToolDefinition,
12
+ RawEvent,
13
+ } from '../protocols/types';
14
+ import { createArkProtocol } from '../protocols/ark';
15
+ import { createDeepSeekProtocol } from '../protocols/deepseek';
16
+ import { createQwenProtocol } from '../protocols/qwen';
17
+ import { createGlmProtocol } from '../protocols/glm';
18
+ import { createMoonshotProtocol } from '../protocols/moonshot';
19
+ import { createMiniMaxProtocol } from '../protocols/minimax';
20
+ import { createVercelGatewayProtocol } from '../protocols/vercel-gateway';
21
+ import { createAnthropicProtocol } from '../protocols/anthropic';
22
+ import { createGeminiProtocol } from '../protocols/gemini';
23
+ import { createOpenAIProtocol } from '../protocols/openai';
24
+ import { createGrokProtocol } from '../protocols/grok';
25
+ import {
26
+ resolveModelFamilyConfig,
27
+ type ModelFamilyConfig,
28
+ } from '../families';
29
+ import type { ProviderAdapter, StreamOnceOptions } from './types';
30
+ import type { LLMConfig, ModelRoute } from '../llm-config';
31
+ import { getRouteChain, resolveRouteUrl } from '../llm-config';
32
+ import { createModuleLogger } from '../logger';
33
+
34
+ const logger = createModuleLogger('ModelAdapter');
35
+
36
+ /** 内置协议工厂 */
37
+ const BUILTIN_PROTOCOL_FACTORIES: Record<string, (config: ProtocolConfig) => Protocol> = {
38
+ ark_v1: createArkProtocol,
39
+ deepseek_v1: createDeepSeekProtocol,
40
+ qwen_v1: createQwenProtocol,
41
+ glm_v1: createGlmProtocol,
42
+ moonshot_v1: createMoonshotProtocol,
43
+ minimax_v1: createMiniMaxProtocol,
44
+ vercel_gateway_v1: createVercelGatewayProtocol,
45
+ anthropic_v1: createAnthropicProtocol,
46
+ gemini_v1: createGeminiProtocol,
47
+ openai_v1: createOpenAIProtocol,
48
+ grok_v1: createGrokProtocol,
49
+ };
50
+
51
+ export class ModelAdapter implements ProviderAdapter {
52
+ readonly name = 'model';
53
+ private llmConfig: LLMConfig;
54
+ /** 合并内置 + 自定义协议工厂 */
55
+ private protocolFactories: Record<string, (config: ProtocolConfig) => Protocol>;
56
+
57
+ constructor(config: LLMConfig) {
58
+ this.llmConfig = config;
59
+ this.protocolFactories = config.protocols
60
+ ? { ...BUILTIN_PROTOCOL_FACTORIES, ...config.protocols }
61
+ : BUILTIN_PROTOCOL_FACTORIES;
62
+ this.validateModels();
63
+ }
64
+
65
+ /** 启动时校验:所有模型的 family 必须可解析,失败立即 throw */
66
+ private validateModels(): void {
67
+ const errors: string[] = [];
68
+ for (const [modelId, modelConfig] of Object.entries(this.llmConfig.models)) {
69
+ if (!modelConfig) continue;
70
+ const familyConfig = resolveModelFamilyConfig(modelId, modelConfig, this.llmConfig);
71
+ if (!familyConfig) {
72
+ errors.push(`模型 ${modelId}: family "${String(modelConfig.family)}" 无法解析,请检查是否在 MODEL_FAMILIES 或 LLMConfig.families 中定义`);
73
+ }
74
+ if (!modelConfig.maxOutputTokens || modelConfig.maxOutputTokens <= 0) {
75
+ errors.push(`模型 ${modelId}: maxOutputTokens 未指定或无效,请为每个模型显式配置`);
76
+ }
77
+ if (!modelConfig.contextWindowTokens || modelConfig.contextWindowTokens <= 0) {
78
+ errors.push(`模型 ${modelId}: contextWindowTokens 未指定或无效,压缩器依赖此值`);
79
+ }
80
+ if (!modelConfig.routes?.length) {
81
+ errors.push(`模型 ${modelId}: routes 为空,至少需要一条路由`);
82
+ }
83
+ for (const route of modelConfig.routes ?? []) {
84
+ if (!this.protocolFactories[route.protocol]) {
85
+ errors.push(`模型 ${modelId}: 协议 "${route.protocol}" 未注册,请检查是否在内置协议或 LLMConfig.protocols 中定义`);
86
+ }
87
+ }
88
+ }
89
+ if (errors.length > 0) {
90
+ throw new Error(`LLMConfig 校验失败:\n${errors.join('\n')}`);
91
+ }
92
+ }
93
+
94
+ private createProtocolForRoute(route: ModelRoute): Protocol {
95
+ const apiUrl = resolveRouteUrl(route);
96
+ const apiKey = route.vendorKey ?? route.accessKey;
97
+ const factory = this.protocolFactories[route.protocol];
98
+ if (!factory) throw new Error(`未知协议: ${route.protocol},请在 LLMConfig.protocols 中注册`);
99
+ return factory({ apiKey, apiUrl });
100
+ }
101
+
102
+ get supportedModels(): string[] {
103
+ return Object.keys(this.llmConfig.models);
104
+ }
105
+
106
+ supportsModel(model: string): boolean {
107
+ return getRouteChain(this.llmConfig, model).length > 0;
108
+ }
109
+
110
+ getModelFamilyConfig(model: string): ModelFamilyConfig | undefined {
111
+ const modelConfig = this.llmConfig.models[model];
112
+ if (!modelConfig) return undefined;
113
+ return resolveModelFamilyConfig(model, modelConfig, this.llmConfig);
114
+ }
115
+
116
+ async *streamOnce(
117
+ messages: ProtocolMessage[],
118
+ tools: ProtocolToolDefinition[],
119
+ options: StreamOnceOptions,
120
+ ): AsyncGenerator<RawEvent> {
121
+ const filtered = messages.filter(
122
+ m => !(m.role === 'assistant' && !m.content && !m.toolCalls?.length),
123
+ );
124
+ yield* this.streamWithFallback(filtered, tools, options);
125
+ }
126
+
127
+ private async *streamWithFallback(
128
+ messages: ProtocolMessage[],
129
+ tools: ProtocolToolDefinition[],
130
+ options: StreamOnceOptions,
131
+ ): AsyncGenerator<RawEvent> {
132
+ const { model } = options;
133
+
134
+ const modelConfig = this.llmConfig.models[model];
135
+ if (!modelConfig) {
136
+ yield { type: 'error', error: `模型 ${model} 未在 LLMConfig.models 中配置` };
137
+ return;
138
+ }
139
+
140
+ const familyConfig = resolveModelFamilyConfig(model, modelConfig, this.llmConfig);
141
+ if (!familyConfig) {
142
+ yield { type: 'error', error: `模型 ${model} 无法解析家族配置,请在 ModelConfig.family 或 LLMConfig.families 中指定` };
143
+ return;
144
+ }
145
+
146
+ const chain = getRouteChain(this.llmConfig, model);
147
+ if (chain.length === 0) {
148
+ yield { type: 'error', error: `模型 ${model} 无可用路径,请检查 LLMConfig.models` };
149
+ return;
150
+ }
151
+
152
+ let lastError = '';
153
+
154
+ for (let i = 0; i < chain.length; i++) {
155
+ const route = chain[i];
156
+ const isLast = i === chain.length - 1;
157
+
158
+ try {
159
+ const protocol = this.createProtocolForRoute(route);
160
+ let committed = false;
161
+
162
+ logger.info({ model, protocol: route.protocol, providerModelId: route.providerModelId, attempt: i + 1, total: chain.length }, '请求 LLM');
163
+
164
+ for await (const event of protocol.stream(messages, tools, {
165
+ model: route.providerModelId,
166
+ familyConfig,
167
+ maxOutputTokens: modelConfig.maxOutputTokens,
168
+ enableThinking: options.enableThinking ?? false,
169
+ signal: options.signal,
170
+ })) {
171
+ if (!committed && event.type === 'error' && !isLast) {
172
+ lastError = event.error ?? 'unknown';
173
+ logger.warn({ protocol: route.protocol, error: lastError }, '路径返回错误,降级');
174
+ break;
175
+ }
176
+
177
+ if (!committed && event.type !== 'error') committed = true;
178
+ yield event;
179
+ // 不在 done 时提前 return:否则内层 protocol.stream 的 async generator
180
+ // 在最后一 yield 之后的收尾(如 GlmProtocol 空流诊断)不会执行。
181
+ }
182
+
183
+ if (committed) return;
184
+ } catch (err) {
185
+ lastError = err instanceof Error ? err.message : String(err);
186
+ if (isLast) {
187
+ yield { type: 'error', error: lastError };
188
+ return;
189
+ }
190
+ logger.warn({ protocol: route.protocol, error: lastError }, '路径异常,尝试降级');
191
+ }
192
+ }
193
+
194
+ yield { type: 'error', error: `所有路径均失败: ${lastError}` };
195
+ }
196
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * 模型选项与上下文展示配置
3
+ *
4
+ * - 从 LLMConfig 推导前端下拉模型列表(capabilities、定价文案、上下文窗口)
5
+ * - formatPricing / formatContextWindow:人类可读展示
6
+ */
7
+
8
+ import type { LLMConfig, ModelConfig, ModelPricing } from '../llm-config';
9
+ import { resolveModelFamilyConfig, type ModelFamilyConfig } from '../families';
10
+
11
+ // ==================== 工具函数 ====================
12
+
13
+ /** 将 ModelPricing 格式化为展示用字符串数组:["¥2/M 输入", "¥3/M 输出"] */
14
+ export function formatPricing(pricing: ModelPricing): string[] {
15
+ const sym = pricing.currency === 'CNY' ? '¥' : '$';
16
+ const fmtNum = (n: number) => Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
17
+ const result: string[] = [];
18
+
19
+ result.push(`${sym}${fmtNum(pricing.input)}/M 输入`);
20
+
21
+ if (Array.isArray(pricing.output)) {
22
+ result.push(`${sym}${fmtNum(pricing.output[0])}~${fmtNum(pricing.output[1])}/M 输出`);
23
+ } else {
24
+ result.push(`${sym}${fmtNum(pricing.output)}/M 输出`);
25
+ }
26
+
27
+ if (pricing.cached != null) {
28
+ result.push(`${sym}${fmtNum(pricing.cached)}/M 缓存`);
29
+ }
30
+
31
+ return result;
32
+ }
33
+
34
+ /** 将 token 数自动推导为人类可读格式:"256K" / "1M" / "10M" */
35
+ export function formatContextWindow(tokens: number): string {
36
+ if (tokens >= 1_000_000 && tokens % 1_000_000 === 0) {
37
+ return `${tokens / 1_000_000}M`;
38
+ }
39
+ if (tokens >= 1_000_000) {
40
+ const m = tokens / 1_000_000;
41
+ return Number.isInteger(m) ? `${m}M` : `${parseFloat(m.toFixed(1))}M`;
42
+ }
43
+ const k = tokens / 1_000;
44
+ return Number.isInteger(k) ? `${k}K` : `${parseFloat(k.toFixed(1))}K`;
45
+ }
46
+
47
+ // ==================== ModelOption(前端显示用) ====================
48
+
49
+ export interface ModelOption {
50
+ modelId: string;
51
+ displayName: string;
52
+ supportsThinking: boolean;
53
+ thinkingAlwaysOn?: boolean;
54
+ supportsVision: boolean;
55
+ /**
56
+ * 模型上下文窗口(token)。
57
+ * 前端据此为 @ 资源区块、历史消息等分配 token 预算,避免拍脑袋硬编码。
58
+ * 与 {@link ModelContextConfig.contextWindowTokens} 来自同一 ModelConfig 字段。
59
+ */
60
+ contextWindowTokens: number;
61
+ /**
62
+ * 单次生成允许的最大输出 token。
63
+ * 前端在估算可用 prompt 预算时需要从 context window 中扣除这部分。
64
+ */
65
+ maxOutputTokens: number;
66
+ tooltip?: {
67
+ features?: string[];
68
+ cost?: string[];
69
+ description?: string;
70
+ };
71
+ }
72
+
73
+ /**
74
+ * 从 LLMConfig 动态构建前端模型列表
75
+ *
76
+ * 只返回 visible !== false 的模型。
77
+ * 所有元数据只从 ModelConfig + familyConfig 取,不查预设库。
78
+ */
79
+ export function buildModelOptions(config: LLMConfig): ModelOption[] {
80
+ const result: ModelOption[] = [];
81
+
82
+ for (const [modelId, modelConfig] of Object.entries(config.models)) {
83
+ if (!modelConfig || modelConfig.visible === false) continue;
84
+
85
+ const familyConfig = resolveModelFamilyConfig(modelId, modelConfig, config);
86
+
87
+ const displayName = modelConfig.displayName ?? modelId;
88
+ const supportsThinking = modelConfig.supportsThinking;
89
+ const supportsVision = modelConfig.supportsVision;
90
+ const thinkingAlwaysOn = familyConfig?.thinkingAlwaysOn ?? false;
91
+
92
+ const features: string[] = [];
93
+ if (supportsVision) features.push('多模态');
94
+ if (supportsThinking) {
95
+ features.push(thinkingAlwaysOn ? '深度思考(始终开启)' : '深度思考');
96
+ }
97
+ if (modelConfig.contextWindowTokens) {
98
+ features.push(`长上下文(${formatContextWindow(modelConfig.contextWindowTokens)})`);
99
+ }
100
+
101
+ result.push({
102
+ modelId,
103
+ displayName,
104
+ supportsThinking,
105
+ thinkingAlwaysOn,
106
+ supportsVision,
107
+ contextWindowTokens: modelConfig.contextWindowTokens,
108
+ maxOutputTokens: modelConfig.maxOutputTokens,
109
+ tooltip: {
110
+ features: features.length > 0 ? features : undefined,
111
+ cost: modelConfig.pricing ? formatPricing(modelConfig.pricing) : undefined,
112
+ },
113
+ });
114
+ }
115
+
116
+ return result;
117
+ }
118
+
119
+ // ==================== ModelContextConfig(运行时 LLMConfig 感知) ====================
120
+
121
+ export interface ModelContextConfig {
122
+ contextWindowTokens: number;
123
+ maxOutputTokens: number;
124
+ }
125
+
126
+ /**
127
+ * 获取模型的 context 配置(从 LLMConfig 感知)
128
+ *
129
+ * 只从 ModelConfig 取,不做注册表 fallback。
130
+ * contextWindowTokens 和 maxOutputTokens 均为必填字段。
131
+ */
132
+ export function getModelContextConfigFromLLM(
133
+ modelId: string,
134
+ config: LLMConfig,
135
+ ): ModelContextConfig | undefined {
136
+ const modelConfig = config.models[modelId];
137
+ if (!modelConfig) return undefined;
138
+
139
+ return {
140
+ contextWindowTokens: modelConfig.contextWindowTokens,
141
+ maxOutputTokens: modelConfig.maxOutputTokens,
142
+ };
143
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Adapter 层类型定义
3
+ *
4
+ * 仅包含适配器接口和配置,不含编排器类型。
5
+ * 编排器类型定义在 orchestrator/types.ts
6
+ */
7
+
8
+ import type {
9
+ RawEvent,
10
+ ProtocolMessage,
11
+ ProtocolToolDefinition,
12
+ } from '../protocols/types';
13
+
14
+ export interface AdapterConfig {
15
+ apiKey: string;
16
+ apiUrl?: string;
17
+ }
18
+
19
+ export interface StreamOnceOptions {
20
+ model: string;
21
+ enableThinking?: boolean;
22
+ signal: AbortSignal;
23
+ }
24
+
25
+ /**
26
+ * Provider Adapter 接口
27
+ *
28
+ * 职责:协议路由 + 消息格式转换 + 返回 RawEvent 流
29
+ */
30
+ export interface ProviderAdapter {
31
+ readonly name: string;
32
+ readonly supportedModels: string[];
33
+
34
+ streamOnce(
35
+ messages: ProtocolMessage[],
36
+ tools: ProtocolToolDefinition[],
37
+ options: StreamOnceOptions
38
+ ): AsyncGenerator<RawEvent>;
39
+
40
+ supportsModel(model: string): boolean;
41
+ }