@astro-minimax/ai 0.9.0 → 0.9.3

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 (251) hide show
  1. package/README.md +108 -18
  2. package/dist/cache/global-cache.d.ts +6 -2
  3. package/dist/cache/global-cache.d.ts.map +1 -1
  4. package/dist/cache/global-cache.js +24 -9
  5. package/dist/cache/index.d.ts +7 -6
  6. package/dist/cache/index.d.ts.map +1 -1
  7. package/dist/cache/index.js +12 -4
  8. package/dist/cache/injection-cache.d.ts +36 -0
  9. package/dist/cache/injection-cache.d.ts.map +1 -0
  10. package/dist/cache/injection-cache.js +90 -0
  11. package/dist/cache/kv-adapter.d.ts.map +1 -1
  12. package/dist/cache/kv-adapter.js +2 -1
  13. package/dist/cache/memory-adapter.d.ts.map +1 -1
  14. package/dist/cache/memory-adapter.js +2 -1
  15. package/dist/cache/response-cache.d.ts +10 -5
  16. package/dist/cache/response-cache.d.ts.map +1 -1
  17. package/dist/cache/response-cache.js +18 -6
  18. package/dist/components/AIChatContainer.d.ts +2 -2
  19. package/dist/components/AIChatContainer.d.ts.map +1 -1
  20. package/dist/components/AIChatContainer.js +8 -920
  21. package/dist/components/ChatInput.d.ts +15 -0
  22. package/dist/components/ChatInput.d.ts.map +1 -0
  23. package/dist/components/ChatInput.js +72 -0
  24. package/dist/components/ChatPanel.d.ts +1 -1
  25. package/dist/components/ChatPanel.d.ts.map +1 -1
  26. package/dist/components/ChatPanel.js +210 -672
  27. package/dist/components/CodeBlock.d.ts +31 -0
  28. package/dist/components/CodeBlock.d.ts.map +1 -0
  29. package/dist/components/CodeBlock.js +143 -0
  30. package/dist/components/MarkmapBlock.d.ts +4 -0
  31. package/dist/components/MarkmapBlock.d.ts.map +1 -0
  32. package/dist/components/MarkmapBlock.js +180 -0
  33. package/dist/components/MermaidBlock.d.ts +4 -0
  34. package/dist/components/MermaidBlock.d.ts.map +1 -0
  35. package/dist/components/MermaidBlock.js +193 -0
  36. package/dist/components/MessageBubble.d.ts +21 -0
  37. package/dist/components/MessageBubble.d.ts.map +1 -0
  38. package/dist/components/MessageBubble.js +233 -0
  39. package/dist/components/ReasoningBlock.d.ts +6 -0
  40. package/dist/components/ReasoningBlock.d.ts.map +1 -0
  41. package/dist/components/ReasoningBlock.js +11 -0
  42. package/dist/components/RichText.d.ts +41 -0
  43. package/dist/components/RichText.d.ts.map +1 -0
  44. package/dist/components/RichText.js +202 -0
  45. package/dist/components/VizShared.d.ts +57 -0
  46. package/dist/components/VizShared.d.ts.map +1 -0
  47. package/dist/components/VizShared.js +233 -0
  48. package/dist/components/tool-auto-continue.d.ts +5 -0
  49. package/dist/components/tool-auto-continue.d.ts.map +1 -0
  50. package/dist/components/tool-auto-continue.js +33 -0
  51. package/dist/constants.d.ts +61 -0
  52. package/dist/constants.d.ts.map +1 -0
  53. package/dist/constants.js +72 -0
  54. package/dist/data/index.d.ts +4 -3
  55. package/dist/data/index.d.ts.map +1 -1
  56. package/dist/data/index.js +4 -10
  57. package/dist/data/knowledge-types.d.ts +8 -0
  58. package/dist/data/knowledge-types.d.ts.map +1 -0
  59. package/dist/data/knowledge-types.js +14 -0
  60. package/dist/data/metadata-loader.d.ts +4 -28
  61. package/dist/data/metadata-loader.d.ts.map +1 -1
  62. package/dist/data/metadata-loader.js +11 -34
  63. package/dist/data/types.d.ts +17 -2
  64. package/dist/data/types.d.ts.map +1 -1
  65. package/dist/extensions/index.d.ts +5 -0
  66. package/dist/extensions/index.d.ts.map +1 -0
  67. package/dist/extensions/index.js +24 -0
  68. package/dist/extensions/injector.d.ts +14 -0
  69. package/dist/extensions/injector.d.ts.map +1 -0
  70. package/dist/extensions/injector.js +146 -0
  71. package/dist/extensions/loader.d.ts +5 -0
  72. package/dist/extensions/loader.d.ts.map +1 -0
  73. package/dist/extensions/loader.js +45 -0
  74. package/dist/extensions/registry.d.ts +4 -0
  75. package/dist/extensions/registry.d.ts.map +1 -0
  76. package/dist/extensions/registry.js +144 -0
  77. package/dist/extensions/types.d.ts +126 -0
  78. package/dist/extensions/types.d.ts.map +1 -0
  79. package/dist/extensions/types.js +0 -0
  80. package/dist/fact-registry/prompt-injector.d.ts +1 -1
  81. package/dist/fact-registry/prompt-injector.d.ts.map +1 -1
  82. package/dist/fact-registry/prompt-injector.js +2 -1
  83. package/dist/index.d.ts +3 -2
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +3 -2
  86. package/dist/intelligence/citation-guard.d.ts +2 -13
  87. package/dist/intelligence/citation-guard.d.ts.map +1 -1
  88. package/dist/intelligence/citation-guard.js +52 -23
  89. package/dist/intelligence/evidence-analysis.d.ts +24 -16
  90. package/dist/intelligence/evidence-analysis.d.ts.map +1 -1
  91. package/dist/intelligence/evidence-analysis.js +118 -20
  92. package/dist/intelligence/evidence-budget.d.ts +13 -0
  93. package/dist/intelligence/evidence-budget.d.ts.map +1 -0
  94. package/dist/intelligence/evidence-budget.js +49 -0
  95. package/dist/intelligence/index.d.ts +10 -4
  96. package/dist/intelligence/index.d.ts.map +1 -1
  97. package/dist/intelligence/index.js +27 -3
  98. package/dist/intelligence/keyword-extract.d.ts +1 -1
  99. package/dist/intelligence/keyword-extract.d.ts.map +1 -1
  100. package/dist/intelligence/keyword-extract.js +5 -9
  101. package/dist/intelligence/request-interpretation.d.ts +40 -0
  102. package/dist/intelligence/request-interpretation.d.ts.map +1 -0
  103. package/dist/intelligence/request-interpretation.js +71 -0
  104. package/dist/intelligence/response-templates.d.ts +1 -0
  105. package/dist/intelligence/response-templates.d.ts.map +1 -1
  106. package/dist/intelligence/response-templates.js +13 -0
  107. package/dist/prompt/dynamic-layer.d.ts +1 -5
  108. package/dist/prompt/dynamic-layer.d.ts.map +1 -1
  109. package/dist/prompt/dynamic-layer.js +145 -9
  110. package/dist/prompt/prompt-builder.d.ts +1 -1
  111. package/dist/prompt/prompt-builder.d.ts.map +1 -1
  112. package/dist/prompt/prompt-builder.js +5 -1
  113. package/dist/prompt/semi-static-layer.d.ts +1 -1
  114. package/dist/prompt/semi-static-layer.d.ts.map +1 -1
  115. package/dist/prompt/semi-static-layer.js +22 -12
  116. package/dist/prompt/static-layer.d.ts.map +1 -1
  117. package/dist/prompt/static-layer.js +37 -4
  118. package/dist/prompt/types.d.ts +9 -4
  119. package/dist/prompt/types.d.ts.map +1 -1
  120. package/dist/provider-manager/base.d.ts +5 -1
  121. package/dist/provider-manager/base.d.ts.map +1 -1
  122. package/dist/provider-manager/base.js +22 -2
  123. package/dist/provider-manager/config.d.ts.map +1 -1
  124. package/dist/provider-manager/config.js +3 -2
  125. package/dist/provider-manager/index.d.ts +1 -1
  126. package/dist/provider-manager/index.d.ts.map +1 -1
  127. package/dist/provider-manager/index.js +1 -2
  128. package/dist/provider-manager/manager.d.ts +10 -1
  129. package/dist/provider-manager/manager.d.ts.map +1 -1
  130. package/dist/provider-manager/manager.js +26 -10
  131. package/dist/provider-manager/openai.d.ts +2 -2
  132. package/dist/provider-manager/openai.d.ts.map +1 -1
  133. package/dist/provider-manager/openai.js +19 -4
  134. package/dist/provider-manager/types.d.ts +18 -38
  135. package/dist/provider-manager/types.d.ts.map +1 -1
  136. package/dist/provider-manager/workers.d.ts +2 -2
  137. package/dist/provider-manager/workers.d.ts.map +1 -1
  138. package/dist/provider-manager/workers.js +15 -4
  139. package/dist/query/followup.d.ts +7 -0
  140. package/dist/query/followup.d.ts.map +1 -0
  141. package/dist/query/followup.js +46 -0
  142. package/dist/query/intent.d.ts +6 -0
  143. package/dist/query/intent.d.ts.map +1 -0
  144. package/dist/query/intent.js +137 -0
  145. package/dist/query/types.d.ts +8 -0
  146. package/dist/query/types.d.ts.map +1 -0
  147. package/dist/query/types.js +0 -0
  148. package/dist/search/hybrid-search.d.ts +111 -0
  149. package/dist/search/hybrid-search.d.ts.map +1 -0
  150. package/dist/search/hybrid-search.js +326 -0
  151. package/dist/search/index.d.ts +11 -9
  152. package/dist/search/index.d.ts.map +1 -1
  153. package/dist/search/index.js +46 -10
  154. package/dist/search/scoring.d.ts +18 -0
  155. package/dist/search/scoring.d.ts.map +1 -0
  156. package/dist/search/{search-utils.js → scoring.js} +14 -27
  157. package/dist/search/search-api.d.ts +16 -1
  158. package/dist/search/search-api.d.ts.map +1 -1
  159. package/dist/search/search-api.js +118 -15
  160. package/dist/search/search-index.d.ts +2 -2
  161. package/dist/search/search-index.d.ts.map +1 -1
  162. package/dist/search/search-index.js +4 -2
  163. package/dist/search/session-cache.d.ts +4 -10
  164. package/dist/search/session-cache.d.ts.map +1 -1
  165. package/dist/search/session-cache.js +12 -45
  166. package/dist/search/types.d.ts +28 -0
  167. package/dist/search/types.d.ts.map +1 -1
  168. package/dist/search/vector-reranker.d.ts +3 -3
  169. package/dist/search/vector-reranker.d.ts.map +1 -1
  170. package/dist/search/vector-reranker.js +14 -2
  171. package/dist/server/chat-handler.d.ts +86 -1
  172. package/dist/server/chat-handler.d.ts.map +1 -1
  173. package/dist/server/chat-handler.js +835 -401
  174. package/dist/server/chat-message-utils.d.ts +6 -0
  175. package/dist/server/chat-message-utils.d.ts.map +1 -0
  176. package/dist/server/chat-message-utils.js +40 -0
  177. package/dist/server/chat-utils.d.ts +30 -0
  178. package/dist/server/chat-utils.d.ts.map +1 -0
  179. package/dist/server/chat-utils.js +88 -0
  180. package/dist/server/dev-server.js +238 -101
  181. package/dist/server/env-config.d.ts +22 -0
  182. package/dist/server/env-config.d.ts.map +1 -0
  183. package/dist/server/env-config.js +25 -0
  184. package/dist/server/errors.d.ts +1 -0
  185. package/dist/server/errors.d.ts.map +1 -1
  186. package/dist/server/errors.js +14 -7
  187. package/dist/server/index.d.ts +2 -4
  188. package/dist/server/index.d.ts.map +1 -1
  189. package/dist/server/index.js +4 -25
  190. package/dist/server/metadata-init.d.ts +10 -5
  191. package/dist/server/metadata-init.d.ts.map +1 -1
  192. package/dist/server/metadata-init.js +78 -34
  193. package/dist/server/notify.d.ts +12 -11
  194. package/dist/server/notify.d.ts.map +1 -1
  195. package/dist/server/notify.js +46 -48
  196. package/dist/server/prompt-runtime.d.ts +60 -0
  197. package/dist/server/prompt-runtime.d.ts.map +1 -0
  198. package/dist/server/prompt-runtime.js +284 -0
  199. package/dist/server/stream-helpers.d.ts +30 -16
  200. package/dist/server/stream-helpers.d.ts.map +1 -1
  201. package/dist/server/stream-helpers.js +152 -15
  202. package/dist/server/types.d.ts +47 -12
  203. package/dist/server/types.d.ts.map +1 -1
  204. package/dist/structured-output/generator.d.ts +6 -0
  205. package/dist/structured-output/generator.d.ts.map +1 -0
  206. package/dist/structured-output/generator.js +164 -0
  207. package/dist/structured-output/index.d.ts +4 -0
  208. package/dist/structured-output/index.d.ts.map +1 -0
  209. package/dist/structured-output/index.js +6 -0
  210. package/dist/structured-output/schemas/evidence.d.ts +88 -0
  211. package/dist/structured-output/schemas/evidence.d.ts.map +1 -0
  212. package/dist/structured-output/schemas/evidence.js +65 -0
  213. package/dist/structured-output/types.d.ts +69 -0
  214. package/dist/structured-output/types.d.ts.map +1 -0
  215. package/dist/structured-output/types.js +0 -0
  216. package/dist/tools/action-tools.d.ts +63 -0
  217. package/dist/tools/action-tools.d.ts.map +1 -0
  218. package/dist/tools/action-tools.js +158 -0
  219. package/dist/tools/index.d.ts +2 -0
  220. package/dist/tools/index.d.ts.map +1 -0
  221. package/dist/tools/index.js +30 -0
  222. package/dist/utils/i18n.d.ts +1 -1
  223. package/dist/utils/i18n.d.ts.map +1 -1
  224. package/dist/utils/i18n.js +1 -1
  225. package/dist/utils/logger.d.ts +11 -0
  226. package/dist/utils/logger.d.ts.map +1 -0
  227. package/dist/utils/logger.js +36 -0
  228. package/dist/utils/text.d.ts +11 -0
  229. package/dist/utils/text.d.ts.map +1 -0
  230. package/dist/utils/text.js +87 -0
  231. package/dist/utils/url.d.ts +19 -0
  232. package/dist/utils/url.d.ts.map +1 -0
  233. package/dist/utils/url.js +13 -0
  234. package/package.json +46 -12
  235. package/dist/intelligence/intent-detect.d.ts +0 -40
  236. package/dist/intelligence/intent-detect.d.ts.map +0 -1
  237. package/dist/intelligence/intent-detect.js +0 -93
  238. package/dist/providers/index.d.ts +0 -2
  239. package/dist/providers/index.d.ts.map +0 -1
  240. package/dist/providers/index.js +0 -5
  241. package/dist/search/search-utils.d.ts +0 -47
  242. package/dist/search/search-utils.d.ts.map +0 -1
  243. package/dist/stream/index.d.ts +0 -3
  244. package/dist/stream/index.d.ts.map +0 -1
  245. package/dist/stream/index.js +0 -8
  246. package/dist/stream/mock-stream.d.ts +0 -12
  247. package/dist/stream/mock-stream.d.ts.map +0 -1
  248. package/dist/stream/mock-stream.js +0 -26
  249. package/dist/stream/response.d.ts +0 -10
  250. package/dist/stream/response.d.ts.map +0 -1
  251. package/dist/stream/response.js +0 -21
@@ -0,0 +1,284 @@
1
+ import { getAuthorContext } from "../data/index.js";
2
+ import { buildSystemPrompt } from "../prompt/index.js";
3
+ import { CHUNK_INJECTION } from "../constants.js";
4
+ import {
5
+ analyzeRetrievedEvidence,
6
+ buildEvidenceSection,
7
+ getCitationGuardPreflight,
8
+ buildUnknownRefusal,
9
+ interpretRequest
10
+ } from "../intelligence/index.js";
11
+ import { matchFactsToQuery, buildFactSection } from "../fact-registry/index.js";
12
+ import {
13
+ resolveVoiceStyleMode,
14
+ buildVoiceStylePrompt,
15
+ mergeFacts
16
+ } from "../extensions/index.js";
17
+ import { buildArticleContextPrompt } from "./chat-utils.js";
18
+ import {
19
+ getArticleChunks
20
+ } from "../search/index.js";
21
+ import { createLogger } from "../utils/logger.js";
22
+ import { extractCodeAnchors } from "../utils/text.js";
23
+ const log = createLogger("prompt-runtime");
24
+ function clipSnippet(text, maxLength = 260) {
25
+ const trimmed = text.trim();
26
+ if (trimmed.length <= maxLength) return trimmed;
27
+ return `${trimmed.slice(0, maxLength).trimEnd()}\u2026`;
28
+ }
29
+ function resolvePromptGuards(args) {
30
+ const { latestText, relatedArticles, relatedProjects, lang } = args;
31
+ const preflight = getCitationGuardPreflight({
32
+ userQuery: latestText,
33
+ articles: relatedArticles,
34
+ projects: relatedProjects,
35
+ lang
36
+ });
37
+ const interpretation = interpretRequest({
38
+ latestText
39
+ });
40
+ const unknownRefusal = interpretation.safety.decision === "refuse" && interpretation.safety.reason === "privacy" ? { text: buildUnknownRefusal(latestText, lang), isUnknown: true } : null;
41
+ return { preflight, unknownRefusal };
42
+ }
43
+ function buildRuntimeSystemPrompt(args) {
44
+ return buildSystemPrompt({
45
+ static: {
46
+ authorName: args.env.SITE_AUTHOR || "\u535A\u4E3B",
47
+ siteUrl: args.env.SITE_URL || "",
48
+ lang: args.lang,
49
+ voiceStylePrompt: args.voiceStylePrompt
50
+ },
51
+ semiStatic: {
52
+ authorContext: getAuthorContext()
53
+ },
54
+ dynamic: {
55
+ userQuery: args.searchQuery,
56
+ articles: args.relatedArticles,
57
+ projects: args.relatedProjects,
58
+ evidenceSection: args.evidenceSection,
59
+ factSection: args.factSection,
60
+ answerMode: args.answerMode,
61
+ lang: args.lang,
62
+ extensions: args.extensions,
63
+ chunksSection: args.chunksSection,
64
+ preferInjectedChunks: args.preferInjectedChunks
65
+ }
66
+ });
67
+ }
68
+ async function assemblePromptRuntime(args) {
69
+ const {
70
+ env,
71
+ latestText,
72
+ context,
73
+ lang,
74
+ evidenceAnalysisTimeout,
75
+ timing,
76
+ adapter,
77
+ hasRealProvider,
78
+ extensions,
79
+ cacheKey,
80
+ searchQuery,
81
+ relatedArticles,
82
+ relatedProjects,
83
+ budget,
84
+ answerMode
85
+ } = args;
86
+ let evidenceSection = "";
87
+ let selectedSources = [];
88
+ if (hasRealProvider && adapter) {
89
+ const evidenceStart = Date.now();
90
+ const abortCtrl = new AbortController();
91
+ const timeoutId = setTimeout(
92
+ () => abortCtrl.abort(),
93
+ evidenceAnalysisTimeout
94
+ );
95
+ try {
96
+ const provider = adapter.getProvider();
97
+ const evidenceResult = await analyzeRetrievedEvidence({
98
+ userQuery: latestText,
99
+ articles: relatedArticles,
100
+ projects: relatedProjects,
101
+ provider,
102
+ model: adapter.evidenceModel,
103
+ maxOutputTokens: budget.analysisMaxTokens,
104
+ abortSignal: abortCtrl.signal
105
+ });
106
+ if (evidenceResult.analysis) {
107
+ evidenceSection = buildEvidenceSection(evidenceResult.analysis);
108
+ }
109
+ log.debug(
110
+ `evidenceAnalysis: status=${evidenceResult.parseStatus}, articles=${relatedArticles.length}, projects=${relatedProjects.length}, analysisLength=${typeof evidenceResult.analysis === "string" ? evidenceResult.analysis.length : 0}, usage=${JSON.stringify(evidenceResult.usage ?? null)}`
111
+ );
112
+ timing.evidenceAnalysis = Date.now() - evidenceStart;
113
+ } catch (error) {
114
+ log.debug(
115
+ `evidenceAnalysis: error=${error instanceof Error ? error.message : String(error)}`
116
+ );
117
+ timing.evidenceAnalysis = Date.now() - evidenceStart;
118
+ } finally {
119
+ clearTimeout(timeoutId);
120
+ }
121
+ }
122
+ const { preflight, unknownRefusal } = resolvePromptGuards({
123
+ latestText,
124
+ relatedArticles,
125
+ relatedProjects,
126
+ lang
127
+ });
128
+ let matchedFacts = matchFactsToQuery(latestText, lang);
129
+ matchedFacts = mergeFacts(matchedFacts, extensions);
130
+ const factPromptSection = buildFactSection(matchedFacts, lang);
131
+ const articleCategories = relatedArticles.flatMap(
132
+ (a) => a.categories ?? []
133
+ );
134
+ const voiceMode = resolveVoiceStyleMode(
135
+ latestText,
136
+ articleCategories,
137
+ extensions
138
+ );
139
+ const voiceStylePrompt = buildVoiceStylePrompt(voiceMode, extensions);
140
+ const articlePrompt = buildArticleContextPrompt(context);
141
+ let chunksSection = "";
142
+ const articleSlugForChunks = context.scope === "article" && context.article?.slug ? context.article.slug : void 0;
143
+ let currentArticleIdForChunks;
144
+ let articlesWithChunks = relatedArticles.filter(
145
+ (a) => Boolean(a.chunks && a.chunks.length > 0)
146
+ );
147
+ if (articleSlugForChunks) {
148
+ currentArticleIdForChunks = articlesWithChunks.find(
149
+ (article) => article.id === articleSlugForChunks || article.url?.includes(articleSlugForChunks)
150
+ )?.id || (getArticleChunks(articleSlugForChunks) ? articleSlugForChunks : getArticleChunks(`zh/${articleSlugForChunks}`) ? `zh/${articleSlugForChunks}` : getArticleChunks(`en/${articleSlugForChunks}`) ? `en/${articleSlugForChunks}` : void 0);
151
+ const currentArticleId = getArticleChunks(articleSlugForChunks) ? articleSlugForChunks : getArticleChunks(`zh/${articleSlugForChunks}`) ? `zh/${articleSlugForChunks}` : getArticleChunks(`en/${articleSlugForChunks}`) ? `en/${articleSlugForChunks}` : void 0;
152
+ const currentChunks = currentArticleId ? getArticleChunks(currentArticleId) : void 0;
153
+ if (currentChunks?.length) {
154
+ const currentArticleUrl = `${env.SITE_URL ?? ""}/${lang}/posts/${articleSlugForChunks}/`;
155
+ const otherArticles = articlesWithChunks.filter(
156
+ (article) => article.id !== currentArticleId && article.id !== articleSlugForChunks && article.url !== currentArticleUrl && !article.url?.includes(articleSlugForChunks)
157
+ );
158
+ articlesWithChunks = [
159
+ {
160
+ id: currentArticleId,
161
+ title: context.article?.title ?? "",
162
+ url: currentArticleUrl,
163
+ lang,
164
+ keyPoints: context.article?.keyPoints ?? [],
165
+ categories: context.article?.categories ?? [],
166
+ dateTime: 0,
167
+ summary: context.article?.summary,
168
+ chunks: currentChunks
169
+ },
170
+ ...otherArticles
171
+ ];
172
+ }
173
+ }
174
+ if (articlesWithChunks.length > 0) {
175
+ const {
176
+ selectRelevantChunks,
177
+ expandChunkMatchesWithNeighbors,
178
+ formatChunksForInjection
179
+ } = await import("../search/hybrid-search.js");
180
+ const { injectionCache } = await import("../cache/injection-cache.js");
181
+ const maxChunksPerArticle = articleSlugForChunks ? CHUNK_INJECTION.MAX_CHUNKS_PER_ARTICLE * 2 : CHUNK_INJECTION.MAX_CHUNKS_PER_ARTICLE;
182
+ const rawAnchors = extractCodeAnchors(latestText);
183
+ const matchedChunks = selectRelevantChunks(latestText, articlesWithChunks, {
184
+ maxTokens: CHUNK_INJECTION.MAX_TOKENS,
185
+ minChunkScore: CHUNK_INJECTION.MIN_CHUNK_SCORE,
186
+ maxChunksPerArticle,
187
+ rawAnchors,
188
+ currentArticleId: currentArticleIdForChunks
189
+ });
190
+ const effectiveMatches = articleSlugForChunks && latestText.length <= 48 ? expandChunkMatchesWithNeighbors(matchedChunks, {
191
+ includePrevious: true,
192
+ includeNext: true,
193
+ rawAnchors
194
+ }) : matchedChunks;
195
+ const prioritizedMatches = articleSlugForChunks ? [
196
+ ...effectiveMatches.filter(
197
+ (match) => match.article.id === articleSlugForChunks || match.article.url?.includes(articleSlugForChunks)
198
+ ),
199
+ ...effectiveMatches.filter(
200
+ (match) => match.article.id !== articleSlugForChunks && !match.article.url?.includes(articleSlugForChunks)
201
+ )
202
+ ] : effectiveMatches;
203
+ if (prioritizedMatches.length > 0) {
204
+ const sessionCacheKey = cacheKey || void 0;
205
+ const newChunks = sessionCacheKey ? injectionCache.filterNewChunks(
206
+ sessionCacheKey,
207
+ prioritizedMatches.map(
208
+ (m) => ({
209
+ id: m.chunk.id,
210
+ content: m.chunk.content
211
+ })
212
+ )
213
+ ) : prioritizedMatches.map(
214
+ (m) => ({
215
+ id: m.chunk.id,
216
+ content: m.chunk.content
217
+ })
218
+ );
219
+ if (newChunks.length > 0) {
220
+ selectedSources = prioritizedMatches.filter(
221
+ (m) => newChunks.some((nc) => nc.id === m.chunk.id)
222
+ ).map(
223
+ (m) => ({
224
+ title: m.article.title,
225
+ url: m.article.url,
226
+ lang: m.article.lang,
227
+ reason: "chunk",
228
+ score: m.score,
229
+ chunkId: m.chunk.id,
230
+ heading: m.chunk.heading,
231
+ snippet: clipSnippet(m.chunk.content),
232
+ matchTerms: rawAnchors.filter(
233
+ (anchor) => m.chunk.content.includes(anchor) || m.chunk.heading.includes(anchor)
234
+ )
235
+ })
236
+ );
237
+ chunksSection = formatChunksForInjection(
238
+ prioritizedMatches.filter(
239
+ (m) => newChunks.some((nc) => nc.id === m.chunk.id)
240
+ ),
241
+ articleSlugForChunks ? 2200 : 1500
242
+ );
243
+ log.debug(
244
+ `chunkSelection: matched=${matchedChunks.length}, effective=${effectiveMatches.length}, prioritized=${prioritizedMatches.length}, new=${newChunks.length}, selectedSources=${selectedSources.length}`
245
+ );
246
+ if (selectedSources.length > 0) {
247
+ log.debug(
248
+ `chunkSelection top: ${selectedSources.slice(0, 5).map(
249
+ (source) => `${source.title}#${source.chunkId ?? "-"}:${(source.score ?? 0).toFixed(3)}`
250
+ ).join(", ")}`
251
+ );
252
+ }
253
+ if (sessionCacheKey) {
254
+ injectionCache.markAsInjected(
255
+ sessionCacheKey,
256
+ newChunks.map((c) => c.id)
257
+ );
258
+ }
259
+ }
260
+ }
261
+ }
262
+ const systemPrompt = buildRuntimeSystemPrompt({
263
+ env,
264
+ lang,
265
+ searchQuery,
266
+ relatedArticles,
267
+ relatedProjects,
268
+ evidenceSection: articlePrompt ? `${evidenceSection}
269
+ ${articlePrompt}` : evidenceSection,
270
+ factSection: factPromptSection,
271
+ answerMode,
272
+ extensions,
273
+ cacheKey,
274
+ voiceStylePrompt,
275
+ chunksSection,
276
+ preferInjectedChunks: !!articleSlugForChunks && !!chunksSection
277
+ });
278
+ return { systemPrompt, preflight, unknownRefusal, selectedSources };
279
+ }
280
+ export {
281
+ assemblePromptRuntime,
282
+ buildRuntimeSystemPrompt,
283
+ resolvePromptGuards
284
+ };
@@ -1,20 +1,16 @@
1
- /**
2
- * Stream helper utilities for chat-handler.
3
- *
4
- * Extracts duplicated stream-writing logic into reusable functions,
5
- * eliminating 34+ `as never` casts and reducing chat-handler.ts size.
6
- */
7
- import { type UIMessage } from 'ai';
8
- import type { TokenUsage } from '@astro-minimax/notify';
9
- import type { ProviderAdapter } from '../provider-manager/types.js';
10
- import type { CachedAIResponse, ResponseCacheConfig } from '../cache/response-cache.js';
11
- type MessageStreamWriter = {
12
- write: (part: unknown) => void;
13
- merge: (stream: ReadableStream) => void;
14
- };
1
+ import { type UIMessage, type UIMessageStreamWriter, type ToolSet } from "ai";
2
+ import type { NotifyTokenUsage as TokenUsage } from "./types.js";
3
+ import type { ProviderAdapter } from "../provider-manager/types.js";
4
+ import type { CachedAIResponse, ResponseCacheConfig } from "../cache/response-cache.js";
5
+ import type { SourceSelection } from "../search/types.js";
6
+ export type MessageStreamWriter = UIMessageStreamWriter<UIMessage>;
15
7
  interface SourceArticle {
16
8
  title: string;
17
9
  url?: string;
10
+ heading?: string;
11
+ snippet?: string;
12
+ score?: number;
13
+ matchTerms?: string[];
18
14
  }
19
15
  interface StreamLLMParams {
20
16
  writer: MessageStreamWriter;
@@ -24,6 +20,7 @@ interface StreamLLMParams {
24
20
  lang: string;
25
21
  temperature?: number;
26
22
  maxOutputTokens?: number;
23
+ tools?: ToolSet;
27
24
  }
28
25
  interface StreamLLMResult {
29
26
  success: boolean;
@@ -31,14 +28,31 @@ interface StreamLLMResult {
31
28
  reasoningText?: string;
32
29
  tokenUsage?: TokenUsage;
33
30
  generationMs: number;
31
+ hadToolCalls?: boolean;
32
+ }
33
+ interface StreamFailoverParams extends Omit<StreamLLMParams, "adapter"> {
34
+ adapters: ProviderAdapter[];
35
+ }
36
+ interface StreamFailoverResult extends StreamLLMResult {
37
+ adapter: ProviderAdapter | null;
38
+ }
39
+ interface StreamAnswerWithFallbackParams extends Omit<StreamFailoverParams, "adapters"> {
40
+ adapters?: ProviderAdapter[];
41
+ question: string;
42
+ }
43
+ export interface StreamAnswerWithFallbackResult extends StreamFailoverResult {
44
+ usedMockFallback: boolean;
34
45
  }
35
46
  export declare function writeSearchStatus(writer: MessageStreamWriter, count: number, lang: string): void;
36
47
  export declare function writeGeneratingStatus(writer: MessageStreamWriter, lang: string, progress?: number): void;
37
48
  export declare function writeDoneStatus(writer: MessageStreamWriter, lang: string): void;
38
- export declare function writeSourceArticles(writer: MessageStreamWriter, articles: SourceArticle[], max?: number): void;
49
+ export declare function writeSourceArticles(writer: MessageStreamWriter, articles: Array<SourceArticle | SourceSelection>, max?: number): void;
50
+ export declare function writeSourceSnippets(writer: MessageStreamWriter, articles: Array<SourceArticle | SourceSelection>, max?: number): void;
39
51
  export declare function writeTextChunk(writer: MessageStreamWriter, text: string, idPrefix?: string): void;
40
- export declare function writeFinish(writer: MessageStreamWriter, reason?: 'stop' | 'error'): void;
52
+ export declare function writeFinish(writer: MessageStreamWriter, reason?: "stop" | "error"): void;
41
53
  export declare function streamLLMResponse(params: StreamLLMParams): Promise<StreamLLMResult>;
54
+ export declare function streamLLMWithFailover(params: StreamFailoverParams): Promise<StreamFailoverResult>;
55
+ export declare function streamAnswerWithFallback(params: StreamAnswerWithFallbackParams): Promise<StreamAnswerWithFallbackResult>;
42
56
  export declare function streamMockFallback(writer: MessageStreamWriter, question: string, lang: string): Promise<string>;
43
57
  export declare function streamCachedResponse(writer: MessageStreamWriter, cachedResponse: CachedAIResponse, config: ResponseCacheConfig, lang: string): Promise<void>;
44
58
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"stream-helpers.d.ts","sourceRoot":"","sources":["../../src/server/stream-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,IAAI,CAAC;AAGZ,OAAO,KAAK,EAAyB,UAAU,EAAe,MAAM,uBAAuB,CAAC;AAC5F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAKpC,KAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACzC,CAAC;AAEF,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,IAAI,CASN;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,SAAK,GACZ,IAAI,CASN;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,MAAM,GACX,IAAI,CAUN;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,aAAa,EAAE,EACzB,GAAG,SAAI,GACN,IAAI,CAWN;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,SAAS,GAChB,IAAI,CAKN;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,mBAAmB,EAC3B,MAAM,GAAE,MAAM,GAAG,OAAgB,GAChC,IAAI,CAEN;AAID,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,eAAe,CAAC,CA6F1B;AAID,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAID,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,mBAAmB,EAC3B,cAAc,EAAE,gBAAgB,EAChC,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAyCf"}
1
+ {"version":3,"file":"stream-helpers.d.ts","sourceRoot":"","sources":["../../src/server/stream-helpers.ts"],"names":[],"mappings":"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"}
@@ -3,8 +3,24 @@ import {
3
3
  convertToModelMessages
4
4
  } from "ai";
5
5
  import { t } from "../utils/i18n.js";
6
+ import { CHAT_HANDLER } from "../constants.js";
6
7
  import { createChatStatusData } from "./types.js";
7
8
  import { createResponsePlaybackGenerator } from "../cache/response-cache.js";
9
+ function getStreamResultMetadata(result) {
10
+ return typeof result === "object" && result !== null ? result : {};
11
+ }
12
+ function streamResultHadToolCalls(result) {
13
+ if (typeof result !== "object" || result === null) return false;
14
+ const candidate = result;
15
+ if (Array.isArray(candidate.steps)) {
16
+ for (const step of candidate.steps) {
17
+ if (Array.isArray(step?.toolCalls) && step.toolCalls.length > 0) {
18
+ return true;
19
+ }
20
+ }
21
+ }
22
+ return Array.isArray(candidate.toolCalls) && candidate.toolCalls.length > 0;
23
+ }
8
24
  function writeSearchStatus(writer, count, lang) {
9
25
  writer.write({
10
26
  type: "message-metadata",
@@ -49,6 +65,27 @@ function writeSourceArticles(writer, articles, max = 3) {
49
65
  }
50
66
  }
51
67
  }
68
+ function writeSourceSnippets(writer, articles, max = 3) {
69
+ for (const article of articles.slice(0, max)) {
70
+ if (!article.snippet) continue;
71
+ try {
72
+ writer.write({
73
+ type: "data-source-snippet",
74
+ id: `snippet-${article.title}-${article.heading ?? "section"}`,
75
+ data: {
76
+ sourceId: `source-${article.title}`,
77
+ title: article.title,
78
+ url: article.url ?? "#",
79
+ heading: article.heading,
80
+ snippet: article.snippet,
81
+ score: article.score,
82
+ matchTerms: article.matchTerms ?? []
83
+ }
84
+ });
85
+ } catch {
86
+ }
87
+ }
88
+ }
52
89
  function writeTextChunk(writer, text, idPrefix = "text") {
53
90
  const id = `${idPrefix}-${Date.now()}`;
54
91
  writer.write({ type: "text-start", id });
@@ -65,18 +102,22 @@ async function streamLLMResponse(params) {
65
102
  systemPrompt,
66
103
  messages,
67
104
  lang,
68
- temperature = 0.3,
69
- maxOutputTokens = 2500
105
+ temperature = CHAT_HANDLER.CACHED_REPLAY_TEMPERATURE,
106
+ maxOutputTokens = CHAT_HANDLER.CACHED_REPLAY_MAX_OUTPUT_TOKENS,
107
+ tools
70
108
  } = params;
71
109
  const start = Date.now();
72
110
  try {
73
111
  const provider = adapter.getProvider();
74
112
  const result = streamText({
75
- model: provider.chatModel(adapter.model),
113
+ model: provider.chatModel(
114
+ adapter.model
115
+ ),
76
116
  system: systemPrompt,
77
117
  messages: await convertToModelMessages(messages),
78
118
  temperature,
79
119
  maxOutputTokens,
120
+ tools,
80
121
  onError: ({ error }) => {
81
122
  console.error("[stream-helpers] streamText error:", error);
82
123
  }
@@ -85,24 +126,29 @@ async function streamLLMResponse(params) {
85
126
  writer.merge(result.toUIMessageStream({ sendFinish: false }));
86
127
  await result.consumeStream({
87
128
  onError: (error) => {
88
- streamErrors.push(error instanceof Error ? error : new Error(String(error)));
129
+ streamErrors.push(
130
+ error instanceof Error ? error : new Error(String(error))
131
+ );
89
132
  }
90
133
  });
91
134
  const text = await result.text;
135
+ const metadata = getStreamResultMetadata(result);
136
+ const hadToolCalls = streamResultHadToolCalls(result);
92
137
  let reasoningText;
93
- const reasoningPromise = result.reasoning;
138
+ const reasoningPromise = metadata.reasoning;
94
139
  if (reasoningPromise) {
95
140
  try {
96
141
  const reasoningOutput = await Promise.resolve(reasoningPromise);
97
142
  reasoningText = typeof reasoningOutput === "string" ? reasoningOutput : Array.isArray(reasoningOutput) ? reasoningOutput.map((r) => {
98
- if (typeof r === "object" && r !== null && "text" in r) return r.text;
143
+ if (typeof r === "object" && r !== null && "text" in r)
144
+ return r.text;
99
145
  return String(r);
100
146
  }).join("") : void 0;
101
147
  } catch {
102
148
  }
103
149
  }
104
150
  let tokenUsage;
105
- const usagePromise = result.usage;
151
+ const usagePromise = metadata.usage;
106
152
  if (usagePromise) {
107
153
  try {
108
154
  const usage = await Promise.resolve(usagePromise);
@@ -121,22 +167,98 @@ async function streamLLMResponse(params) {
121
167
  adapter.recordFailure(streamErrors[0]);
122
168
  writeTextChunk(writer, t("ai.error.generic", lang), "error");
123
169
  writeFinish(writer, "error");
124
- return { success: true, responseText: text, reasoningText, tokenUsage, generationMs };
170
+ return {
171
+ success: false,
172
+ responseText: text,
173
+ reasoningText,
174
+ tokenUsage,
175
+ generationMs,
176
+ hadToolCalls
177
+ };
125
178
  }
126
179
  if (text.length > 0) {
127
180
  adapter.recordSuccess();
128
181
  writeFinish(writer);
129
- return { success: true, responseText: text, reasoningText, tokenUsage, generationMs };
182
+ return {
183
+ success: true,
184
+ responseText: text,
185
+ reasoningText,
186
+ tokenUsage,
187
+ generationMs,
188
+ hadToolCalls
189
+ };
190
+ }
191
+ if (hadToolCalls) {
192
+ adapter.recordSuccess();
193
+ writeFinish(writer);
194
+ return {
195
+ success: true,
196
+ responseText: "",
197
+ reasoningText,
198
+ tokenUsage,
199
+ generationMs,
200
+ hadToolCalls
201
+ };
130
202
  }
131
203
  writeTextChunk(writer, t("ai.error.noOutput", lang), "no-output");
132
204
  writeFinish(writer);
133
- return { success: true, responseText: "", reasoningText, tokenUsage, generationMs };
205
+ return {
206
+ success: true,
207
+ responseText: "",
208
+ reasoningText,
209
+ tokenUsage,
210
+ generationMs,
211
+ hadToolCalls
212
+ };
134
213
  } catch (err) {
135
214
  adapter.recordFailure(err instanceof Error ? err : new Error(String(err)));
136
215
  console.error("[stream-helpers] Provider threw:", err.message);
137
- return { success: false, responseText: "", generationMs: Date.now() - start };
216
+ return {
217
+ success: false,
218
+ responseText: "",
219
+ generationMs: Date.now() - start,
220
+ hadToolCalls: false
221
+ };
138
222
  }
139
223
  }
224
+ async function streamLLMWithFailover(params) {
225
+ const { adapters, ...streamParams } = params;
226
+ for (const adapter of adapters) {
227
+ const result = await streamLLMResponse({ ...streamParams, adapter });
228
+ if (result.success && (result.responseText.length > 0 || result.hadToolCalls)) {
229
+ return { ...result, adapter };
230
+ }
231
+ }
232
+ return {
233
+ success: false,
234
+ responseText: "",
235
+ generationMs: 0,
236
+ adapter: null
237
+ };
238
+ }
239
+ async function streamAnswerWithFallback(params) {
240
+ const { writer, adapters = [], question, lang, ...streamParams } = params;
241
+ const llmResult = await streamLLMWithFailover({
242
+ writer,
243
+ adapters,
244
+ lang,
245
+ ...streamParams
246
+ });
247
+ if (llmResult.adapter) {
248
+ return {
249
+ ...llmResult,
250
+ usedMockFallback: false
251
+ };
252
+ }
253
+ const responseText = await streamMockFallback(writer, question, lang);
254
+ return {
255
+ success: true,
256
+ responseText,
257
+ generationMs: llmResult.generationMs,
258
+ adapter: null,
259
+ usedMockFallback: true
260
+ };
261
+ }
140
262
  async function streamMockFallback(writer, question, lang) {
141
263
  const { getMockResponse } = await import("../providers/mock.js");
142
264
  const mockText = getMockResponse(question, lang);
@@ -153,11 +275,19 @@ async function streamMockFallback(writer, question, lang) {
153
275
  return mockText;
154
276
  }
155
277
  async function streamCachedResponse(writer, cachedResponse, config, lang) {
156
- writeSearchStatus(writer, cachedResponse.articles.length + cachedResponse.projects.length, lang);
278
+ writeSearchStatus(
279
+ writer,
280
+ cachedResponse.articles.length + cachedResponse.projects.length,
281
+ lang
282
+ );
157
283
  writeGeneratingStatus(writer, lang);
158
- writeSourceArticles(writer, cachedResponse.articles);
284
+ writeSourceArticles(writer, cachedResponse.sources);
285
+ writeSourceSnippets(writer, cachedResponse.sources);
159
286
  writeGeneratingStatus(writer, lang, 70);
160
- const playbackGenerator = createResponsePlaybackGenerator(cachedResponse, config);
287
+ const playbackGenerator = createResponsePlaybackGenerator(
288
+ cachedResponse,
289
+ config
290
+ );
161
291
  let thinkingId;
162
292
  const textId = `text-${Date.now()}`;
163
293
  let textStarted = false;
@@ -167,7 +297,11 @@ async function streamCachedResponse(writer, cachedResponse, config, lang) {
167
297
  thinkingId = `thinking-${Date.now()}`;
168
298
  writer.write({ type: "reasoning-start", id: thinkingId });
169
299
  }
170
- writer.write({ type: "reasoning-delta", id: thinkingId, delta: chunk.text });
300
+ writer.write({
301
+ type: "reasoning-delta",
302
+ id: thinkingId,
303
+ delta: chunk.text
304
+ });
171
305
  } else {
172
306
  if (thinkingId) {
173
307
  writer.write({ type: "reasoning-end", id: thinkingId });
@@ -190,13 +324,16 @@ async function streamCachedResponse(writer, cachedResponse, config, lang) {
190
324
  writeFinish(writer);
191
325
  }
192
326
  export {
327
+ streamAnswerWithFallback,
193
328
  streamCachedResponse,
194
329
  streamLLMResponse,
330
+ streamLLMWithFailover,
195
331
  streamMockFallback,
196
332
  writeDoneStatus,
197
333
  writeFinish,
198
334
  writeGeneratingStatus,
199
335
  writeSearchStatus,
200
336
  writeSourceArticles,
337
+ writeSourceSnippets,
201
338
  writeTextChunk
202
339
  };