@geminilight/mindos 0.6.63 → 0.6.65

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 (268) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +21 -21
  4. package/_standalone/.next/build-manifest.json +2 -2
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  10. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error.html +2 -2
  12. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  25. package/_standalone/.next/server/app/agents/page.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  27. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  28. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/agents/copy-skill/route.js.nft.json +1 -1
  39. package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/ask/route.js +48 -42
  43. package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
  44. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
  48. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
  50. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
  52. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
  54. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/file/import/route.js +1 -1
  57. package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
  58. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/file/raw/route.js.nft.json +1 -1
  60. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
  62. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  64. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  66. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
  68. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
  71. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
  73. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/mcp/agents/route.js +1 -1
  75. package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
  76. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
  83. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
  85. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  87. package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  88. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/settings/list-models/route.js +1 -1
  90. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/settings/route.js +1 -1
  93. package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  94. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/settings/test-key/route.js +1 -1
  96. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  97. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  99. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/api/setup/route.js +1 -1
  102. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/api/skills/route.js +1 -1
  104. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  105. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  106. package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
  107. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  108. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  109. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  110. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  111. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  112. package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
  113. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  114. package/_standalone/.next/server/app/changes/page.js +1 -1
  115. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  116. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  117. package/_standalone/.next/server/app/echo/[segment]/page.js +2 -2
  118. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  119. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  120. package/_standalone/.next/server/app/echo/page.js +1 -1
  121. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  122. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  123. package/_standalone/.next/server/app/explore/page.js +1 -1
  124. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  125. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  126. package/_standalone/.next/server/app/help/page.js +1 -1
  127. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  128. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  129. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  130. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  131. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  132. package/_standalone/.next/server/app/login/page.js +1 -1
  133. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  134. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  135. package/_standalone/.next/server/app/page.js +1 -1
  136. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  137. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  138. package/_standalone/.next/server/app/setup/page.js +2 -2
  139. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  140. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  141. package/_standalone/.next/server/app/trash/page.js +3 -3
  142. package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
  143. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  144. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  145. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  146. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  147. package/_standalone/.next/server/app/wiki/page.js +1 -1
  148. package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
  149. package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
  150. package/_standalone/.next/server/app-paths-manifest.json +21 -21
  151. package/_standalone/.next/server/chunks/122.js +222 -0
  152. package/_standalone/.next/server/chunks/3113.js +52 -0
  153. package/_standalone/.next/server/chunks/6539.js +1 -1
  154. package/_standalone/.next/server/chunks/8388.js +2 -2
  155. package/_standalone/.next/server/chunks/953.js +3 -3
  156. package/_standalone/.next/server/chunks/9787.js +2 -0
  157. package/_standalone/.next/server/pages/500.html +2 -2
  158. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  159. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/_standalone/.next/static/chunks/1001-99da82ec8d8c136f.js +1 -0
  161. package/_standalone/.next/static/chunks/5149-4d828886dda479fa.js +1 -0
  162. package/_standalone/.next/static/chunks/{5581-82e5db227f8e9393.js → 5581-c671163a2fe1b312.js} +2 -2
  163. package/_standalone/.next/static/chunks/6636-53238eff89503f03.js +6 -0
  164. package/_standalone/.next/static/chunks/6757-1c1a89720fdda8f0.js +1 -0
  165. package/_standalone/.next/static/chunks/7129-20e9d2463a9da646.js +1 -0
  166. package/_standalone/.next/static/chunks/{3674-be69a8b858ceacdd.js → 7294-cac25d97869afadc.js} +1 -1
  167. package/_standalone/.next/static/chunks/8225-21e5cebc3731ddf0.js +1 -0
  168. package/_standalone/.next/static/chunks/8520-b51810e66293ceb8.js +22 -0
  169. package/_standalone/.next/static/chunks/9207-dc9c31b351a2ed78.js +1 -0
  170. package/_standalone/.next/static/chunks/app/agents/[agentKey]/{page-b0dabe793500383d.js → page-2f5cf97e03dc1cc9.js} +1 -1
  171. package/_standalone/.next/static/chunks/app/agents/{page-1f1ac330c8177cf6.js → page-50eac58d511dcc6e.js} +1 -1
  172. package/_standalone/.next/static/chunks/app/echo/[segment]/page-2a00f4686adf3885.js +11 -0
  173. package/_standalone/.next/static/chunks/app/{layout-50a6b1164ee98ab9.js → layout-2cb7a6602d2e5d5f.js} +62 -58
  174. package/_standalone/.next/static/chunks/app/{page-73802bd31d7f6c9f.js → page-5ab911b2226f6ff7.js} +1 -1
  175. package/_standalone/.next/static/chunks/app/setup/page-907b7c57fad2292b.js +1 -0
  176. package/_standalone/.next/static/chunks/app/trash/page-11a511b065ea84c2.js +1 -0
  177. package/_standalone/.next/static/chunks/app/view/[...path]/{page-808f39963bf04715.js → page-26e47dd4c533a58c.js} +2 -2
  178. package/_standalone/.next/static/css/67e7918f5ed7d147.css +1 -0
  179. package/_standalone/.next/trace +65 -65
  180. package/_standalone/__tests__/api/ask-attachments.test.ts +194 -0
  181. package/_standalone/__tests__/api/settings.test.ts +16 -12
  182. package/_standalone/__tests__/api/setup.test.ts +11 -9
  183. package/_standalone/__tests__/api/test-key.test.ts +0 -10
  184. package/_standalone/__tests__/components/UpdateToast.test.ts +344 -0
  185. package/_standalone/__tests__/core/context.test.ts +48 -426
  186. package/_standalone/__tests__/lib/pi-skills.test.ts +4 -4
  187. package/_standalone/__tests__/lib/settings-ai-client.test.ts +32 -12
  188. package/_standalone/__tests__/setup.ts +5 -5
  189. package/_standalone/components/ask/AskContent.tsx +70 -40
  190. package/_standalone/components/ask/AskHeader.tsx +8 -1
  191. package/_standalone/components/ask/MessageList.tsx +37 -3
  192. package/_standalone/components/ask/ProviderModelCapsule.tsx +51 -129
  193. package/_standalone/components/settings/AiTab.tsx +270 -347
  194. package/_standalone/components/settings/CustomProviderFields.tsx +121 -0
  195. package/_standalone/components/settings/CustomProvidersCard.tsx +2 -2
  196. package/_standalone/components/settings/KnowledgeTab.tsx +6 -20
  197. package/_standalone/components/settings/McpAgentInstall.tsx +7 -2
  198. package/_standalone/components/settings/Primitives.tsx +48 -104
  199. package/_standalone/components/settings/ProviderModal.tsx +38 -221
  200. package/_standalone/components/settings/SettingsContent.tsx +5 -12
  201. package/_standalone/components/settings/TestButton.tsx +64 -0
  202. package/_standalone/components/settings/types.ts +3 -12
  203. package/_standalone/components/settings/useCustomProviderForm.ts +132 -0
  204. package/_standalone/components/setup/StepAI.tsx +3 -3
  205. package/_standalone/components/shared/ModelInput.tsx +18 -4
  206. package/_standalone/components/shared/ProviderSelect.tsx +126 -134
  207. package/_standalone/hooks/useAskChat.ts +97 -13
  208. package/_standalone/hooks/useAskPanel.ts +17 -1
  209. package/_standalone/lib/settings-ai-client.ts +17 -8
  210. package/_standalone/tsconfig.tsbuildinfo +1 -1
  211. package/app/app/api/ask/route.ts +124 -44
  212. package/app/app/api/mcp/agents/route.ts +3 -3
  213. package/app/app/api/settings/list-models/route.ts +15 -26
  214. package/app/app/api/settings/route.ts +14 -59
  215. package/app/app/api/settings/test-key/route.ts +47 -12
  216. package/app/app/api/setup/route.ts +36 -18
  217. package/app/app/api/skills/route.ts +1 -1
  218. package/app/app/layout.tsx +5 -3
  219. package/app/components/HomeContent.tsx +11 -0
  220. package/app/components/UpdateToast.tsx +255 -0
  221. package/app/components/ask/AskContent.tsx +70 -40
  222. package/app/components/ask/AskHeader.tsx +8 -1
  223. package/app/components/ask/MessageList.tsx +37 -3
  224. package/app/components/ask/ProviderModelCapsule.tsx +51 -129
  225. package/app/components/settings/AiTab.tsx +270 -347
  226. package/app/components/settings/CustomProviderFields.tsx +121 -0
  227. package/app/components/settings/CustomProvidersCard.tsx +2 -2
  228. package/app/components/settings/KnowledgeTab.tsx +6 -20
  229. package/app/components/settings/McpAgentInstall.tsx +7 -2
  230. package/app/components/settings/Primitives.tsx +48 -104
  231. package/app/components/settings/ProviderModal.tsx +38 -221
  232. package/app/components/settings/SettingsContent.tsx +5 -12
  233. package/app/components/settings/TestButton.tsx +64 -0
  234. package/app/components/settings/types.ts +3 -12
  235. package/app/components/settings/useCustomProviderForm.ts +132 -0
  236. package/app/components/setup/StepAI.tsx +3 -3
  237. package/app/components/shared/ModelInput.tsx +18 -4
  238. package/app/components/shared/ProviderSelect.tsx +126 -134
  239. package/app/hooks/useAskChat.ts +97 -13
  240. package/app/hooks/useAskPanel.ts +17 -1
  241. package/app/lib/agent/context.ts +65 -0
  242. package/app/lib/agent/providers.ts +25 -0
  243. package/app/lib/agent/tools.ts +1 -1
  244. package/app/lib/custom-endpoints.ts +129 -29
  245. package/app/lib/i18n/modules/settings.ts +20 -0
  246. package/app/lib/pi-integration/skills.ts +16 -4
  247. package/app/lib/settings-ai-client.ts +17 -8
  248. package/app/lib/settings.ts +64 -90
  249. package/app/lib/types.ts +4 -0
  250. package/package.json +1 -1
  251. package/_standalone/.next/server/chunks/530.js +0 -218
  252. package/_standalone/.next/server/chunks/9007.js +0 -2
  253. package/_standalone/.next/server/chunks/9137.js +0 -52
  254. package/_standalone/.next/static/chunks/1309-373ade1b40aea186.js +0 -1
  255. package/_standalone/.next/static/chunks/3165-9189a38fd9ebf6f2.js +0 -1
  256. package/_standalone/.next/static/chunks/4587-5d06728133fff222.js +0 -1
  257. package/_standalone/.next/static/chunks/6261-5ce86db54b19ae46.js +0 -1
  258. package/_standalone/.next/static/chunks/6636-9bbc90fb3b8731fe.js +0 -6
  259. package/_standalone/.next/static/chunks/7637-904b0a381dc3ec02.js +0 -1
  260. package/_standalone/.next/static/chunks/8520-84e607f33c409f91.js +0 -22
  261. package/_standalone/.next/static/chunks/9207-9a4a1a1ede4f8e6e.js +0 -1
  262. package/_standalone/.next/static/chunks/app/echo/[segment]/page-bc5e104eb7ae6327.js +0 -11
  263. package/_standalone/.next/static/chunks/app/setup/page-79acb0baf38184c6.js +0 -1
  264. package/_standalone/.next/static/chunks/app/trash/page-d040db56863da504.js +0 -1
  265. package/_standalone/.next/static/css/1287672978833d07.css +0 -1
  266. package/_standalone/lib/agent/context.ts +0 -403
  267. /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_buildManifest.js +0 -0
  268. /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_ssgManifest.js +0 -0
@@ -1,403 +0,0 @@
1
- /**
2
- * Phase 3: Context management — token estimation, compaction, tool output truncation.
3
- *
4
- * All operations are request-scoped (no persistence to frontend session).
5
- * Uses pi-ai types (AgentMessage from pi-agent-core, complete from pi-ai).
6
- */
7
- import { complete, type Model } from '@mariozechner/pi-ai';
8
- import type { AgentMessage } from '@mariozechner/pi-agent-core';
9
- import type { ToolResultMessage, AssistantMessage, UserMessage } from '@mariozechner/pi-ai';
10
- import { countCjkChars } from '@/lib/core/cjk';
11
-
12
- const DEV = process.env.NODE_ENV === 'development';
13
-
14
- // AgentMessage is opaque; cast to access role/content at runtime.
15
- interface AgentMessageFields {
16
- role: string;
17
- content: string | Array<{ type: string; text?: string }>;
18
- }
19
- function asMsg(m: AgentMessage): AgentMessageFields { return m as unknown as AgentMessageFields; }
20
-
21
- // ---------------------------------------------------------------------------
22
- // Token estimation — CJK-aware (CJK ~1.5 tokens/char, ASCII ~0.25 tokens/char)
23
- // ---------------------------------------------------------------------------
24
-
25
- /**
26
- * Estimate token count for a string using character-class heuristics.
27
- * - CJK characters: ~1.5 tokens per character (measured against cl100k_base)
28
- * - ASCII/Latin: ~0.25 tokens per character (1 token ≈ 4 chars)
29
- * This is 3-4x more accurate than naive length/4 for mixed CJK/English text.
30
- */
31
- export function estimateStringTokens(text: string): number {
32
- const cjkCount = countCjkChars(text);
33
- const nonCjkCount = text.length - cjkCount;
34
- return Math.ceil(cjkCount * 1.5 + nonCjkCount / 4);
35
- }
36
-
37
- /** Rough token count for a single AgentMessage */
38
- function messageTokens(msg: AgentMessage): number {
39
- if ('content' in msg) {
40
- const content = asMsg(msg).content;
41
- if (typeof content === 'string') return estimateStringTokens(content);
42
- if (Array.isArray(content)) {
43
- let tokens = 0;
44
- for (const part of content) {
45
- if ('text' in part && typeof part.text === 'string') tokens += estimateStringTokens(part.text);
46
- if ('args' in part) tokens += estimateStringTokens(JSON.stringify(part.args));
47
- }
48
- return tokens;
49
- }
50
- }
51
- return 0;
52
- }
53
-
54
- /** Estimate total tokens for a message array */
55
- export function estimateTokens(messages: AgentMessage[]): number {
56
- let total = 0;
57
- for (const m of messages) total += messageTokens(m);
58
- return total;
59
- }
60
-
61
- // ---------------------------------------------------------------------------
62
- // Context limits by model family
63
- // ---------------------------------------------------------------------------
64
-
65
- const MODEL_LIMITS: Record<string, number> = {
66
- 'claude': 200_000,
67
- 'gpt-4o': 128_000,
68
- 'gpt-4': 128_000,
69
- 'gpt-3.5': 16_000,
70
- 'gpt-5': 200_000,
71
- };
72
-
73
- // Sort by prefix length descending so "gpt-4o" matches before "gpt-4"
74
- const MODEL_LIMIT_ENTRIES = Object.entries(MODEL_LIMITS)
75
- .sort((a, b) => b[0].length - a[0].length);
76
-
77
- /** Get context token limit for a model string */
78
- export function getContextLimit(model: string): number {
79
- const lower = model.toLowerCase();
80
- for (const [prefix, limit] of MODEL_LIMIT_ENTRIES) {
81
- if (lower.includes(prefix)) return limit;
82
- }
83
- return 100_000; // conservative default
84
- }
85
-
86
- /** Check if messages + system prompt exceed threshold of context limit */
87
- export function needsCompact(
88
- messages: AgentMessage[],
89
- systemPrompt: string,
90
- model: string,
91
- threshold = 0.7,
92
- ): boolean {
93
- const total = estimateTokens(messages) + estimateStringTokens(systemPrompt);
94
- const limit = getContextLimit(model);
95
- return total > limit * threshold;
96
- }
97
-
98
- // ---------------------------------------------------------------------------
99
- // Tool output truncation (per-tool-type thresholds)
100
- // ---------------------------------------------------------------------------
101
-
102
- const TOOL_OUTPUT_LIMITS: Record<string, number> = {
103
- // List/search tools — only need to know "what was found"
104
- search: 500,
105
- list_files: 500,
106
- get_recent: 500,
107
- get_backlinks: 500,
108
- get_history: 500,
109
- // Read tools — some context value, but not full file
110
- read_file: 2000,
111
- get_file_at_version: 2000,
112
- // Write tools — only need success/failure
113
- write_file: 200,
114
- create_file: 200,
115
- delete_file: 200,
116
- rename_file: 200,
117
- move_file: 200,
118
- append_to_file: 200,
119
- insert_after_heading: 200,
120
- update_section: 200,
121
- append_csv: 200,
122
- };
123
-
124
- /**
125
- * Truncate tool outputs in historical messages to save tokens.
126
- * Only truncates non-last toolResult messages (the last one is kept intact
127
- * because the model may need its full output for the current step).
128
- */
129
- export function truncateToolOutputs(messages: AgentMessage[]): AgentMessage[] {
130
- // Find the index of the last 'toolResult' role message
131
- let lastToolIdx = -1;
132
- for (let i = messages.length - 1; i >= 0; i--) {
133
- if (asMsg(messages[i]).role === 'toolResult') { lastToolIdx = i; break; }
134
- }
135
-
136
- return messages.map((msg, idx) => {
137
- const m = asMsg(msg);
138
- if (m.role !== 'toolResult' || idx === lastToolIdx) return msg;
139
-
140
- const toolMsg = m as ToolResultMessage;
141
- const toolName = toolMsg.toolName ?? '';
142
- const limit = TOOL_OUTPUT_LIMITS[toolName] ?? 500;
143
-
144
- // Truncate text content in toolResult
145
- const truncatedContent = toolMsg.content.map(part => {
146
- if (part.type !== 'text') return part;
147
- if (part.text.length <= limit) return part;
148
- return {
149
- ...part,
150
- text: part.text.slice(0, limit) + `\n[...truncated from ${part.text.length} chars]`,
151
- };
152
- });
153
-
154
- return { ...toolMsg, content: truncatedContent } as AgentMessage;
155
- });
156
- }
157
-
158
- // ---------------------------------------------------------------------------
159
- // Compact: summarize early messages via LLM
160
- // ---------------------------------------------------------------------------
161
-
162
- const COMPACT_PROMPT = `Summarize the key points, decisions, and file operations from this conversation in under 500 words. Focus on:
163
- - What the user asked for
164
- - What files were read, created, or modified
165
- - Key decisions and outcomes
166
- - Any unresolved issues
167
-
168
- Be concise and factual. Output only the summary, no preamble.`;
169
-
170
- /** Extract a short text representation from an AgentMessage for summarization */
171
- function messageToText(m: AgentMessage): string {
172
- const msg = asMsg(m);
173
- const role = msg.role;
174
- let content = '';
175
-
176
- if (typeof msg.content === 'string') {
177
- content = msg.content;
178
- } else if (Array.isArray(msg.content)) {
179
- const pieces: string[] = [];
180
- for (const part of msg.content) {
181
- if (part.type === 'text' && typeof part.text === 'string') {
182
- pieces.push(part.text);
183
- } else if (part.type === 'toolCall' && 'toolName' in part) {
184
- pieces.push(`[Tool: ${part.toolName}]`);
185
- }
186
- }
187
- content = pieces.filter(Boolean).join(' ');
188
- }
189
- return `${role}: ${content}`;
190
- }
191
-
192
- /**
193
- * Compact messages by summarizing early ones with LLM.
194
- * Returns a new message array with early messages replaced by a summary.
195
- * Only called when needsCompact() returns true.
196
- *
197
- * Uses pi-ai complete() for summarization.
198
- */
199
- export async function compactMessages(
200
- messages: AgentMessage[],
201
- model: Model<any>,
202
- apiKey: string,
203
- systemPrompt: string,
204
- modelName: string,
205
- ): Promise<{ messages: AgentMessage[]; compacted: boolean }> {
206
- if (messages.length < 6) {
207
- return { messages, compacted: false };
208
- }
209
-
210
- // Keep the last 6 messages intact, summarize the rest.
211
- // Adjust split point to avoid cutting between an assistant (with tool calls)
212
- // and its tool result.
213
- let splitIdx = messages.length - 6;
214
- while (splitIdx > 0 && asMsg(messages[splitIdx]).role === 'toolResult') {
215
- splitIdx--;
216
- }
217
- if (splitIdx < 2) {
218
- return { messages, compacted: false };
219
- }
220
- const earlyMessages = messages.slice(0, splitIdx);
221
- const recentMessages = messages.slice(splitIdx);
222
-
223
- // Build a text representation of early messages for summarization
224
- let earlyText = earlyMessages.map(messageToText).join('\n\n');
225
-
226
- // Truncate if enormous (avoid sending too much to summarizer)
227
- if (earlyText.length > 30_000) {
228
- earlyText = earlyText.slice(0, 30_000) + '\n[...truncated]';
229
- }
230
-
231
- try {
232
- const summaryMessage = await complete(model, {
233
- messages: [{
234
- role: 'user',
235
- content: `${COMPACT_PROMPT}\n\n---\n\nConversation to summarize:\n\n${earlyText}`,
236
- timestamp: Date.now(),
237
- }],
238
- }, { apiKey });
239
-
240
- const summaryText = summaryMessage.content
241
- .filter(p => p.type === 'text')
242
- .map(p => (p as { text?: string }).text)
243
- .join('');
244
-
245
- if (DEV) console.log(`[ask] Compacted ${earlyMessages.length} early messages into summary (${summaryText.length} chars)`);
246
-
247
- const summaryContent = `[System Note: Older conversation history has been truncated due to context length limits, but here is an AI-generated summary of what was discussed so far.]\n\n${summaryText}`;
248
-
249
- // If first recent message is also 'user', merge summary into it to avoid
250
- // consecutive user messages (Anthropic rejects user→user sequences).
251
- if (asMsg(recentMessages[0])?.role === 'user') {
252
- const merged = { ...asMsg(recentMessages[0]) } as AgentMessageFields;
253
- if (typeof merged.content === 'string') {
254
- merged.content = `${summaryContent}\n\n---\n\n${merged.content}`;
255
- } else if (Array.isArray(merged.content)) {
256
- merged.content = [{ type: 'text' as const, text: `${summaryContent}\n\n---\n\n` }, ...merged.content];
257
- } else {
258
- merged.content = summaryContent;
259
- }
260
- return {
261
- messages: [merged as AgentMessage, ...recentMessages.slice(1)],
262
- compacted: true,
263
- };
264
- }
265
-
266
- // Otherwise prepend as separate user message
267
- const summaryMsg: UserMessage = {
268
- role: 'user',
269
- content: summaryContent,
270
- timestamp: Date.now(),
271
- };
272
-
273
- return {
274
- messages: [summaryMsg as AgentMessage, ...recentMessages],
275
- compacted: true,
276
- };
277
- } catch (err) {
278
- // API failure: fall back to hard prune instead of risking context overflow
279
- console.warn('[ask] Compact failed, applying hard prune as fallback:', err);
280
- const pruned = hardPrune(messages, systemPrompt, modelName);
281
- if (pruned.length < messages.length) {
282
- if (DEV) console.log(`[ask] Hard prune fallback succeeded (${messages.length} → ${pruned.length} messages)`);
283
- return { messages: pruned, compacted: false };
284
- }
285
- // If pruning also can't help, let it bubble up so request fails safely
286
- throw new Error(`Context compaction failed and pruning insufficient: ${err instanceof Error ? err.message : String(err)}`);
287
- }
288
- }
289
-
290
- // ---------------------------------------------------------------------------
291
- // Hard prune: drop earliest messages as last resort (>90% context)
292
- // ---------------------------------------------------------------------------
293
-
294
- /**
295
- * Hard prune: if still over 90% context after compact, drop earliest messages.
296
- * Respects assistant-tool pairs: never cuts between an assistant message
297
- * (containing tool calls) and its following tool result message.
298
- */
299
- export function hardPrune(
300
- messages: AgentMessage[],
301
- systemPrompt: string,
302
- model: string,
303
- ): AgentMessage[] {
304
- const limit = getContextLimit(model);
305
- const threshold = limit * 0.9;
306
- const systemTokens = estimateStringTokens(systemPrompt);
307
-
308
- let total = systemTokens + estimateTokens(messages);
309
- if (total <= threshold) return messages;
310
-
311
- // Find the cut index: keep messages from cutIdx onward
312
- let cutIdx = 0;
313
- while (cutIdx < messages.length - 2 && total > threshold) {
314
- total -= messageTokens(messages[cutIdx]);
315
- cutIdx++;
316
- }
317
-
318
- // Ensure we don't cut between an assistant (with tool calls) and its tool result.
319
- while (cutIdx < messages.length - 1 && asMsg(messages[cutIdx]).role === 'toolResult') {
320
- total -= messageTokens(messages[cutIdx]);
321
- cutIdx++;
322
- }
323
-
324
- // Ensure first message is 'user' (Anthropic requirement)
325
- while (cutIdx < messages.length - 1 && asMsg(messages[cutIdx]).role !== 'user') {
326
- total -= messageTokens(messages[cutIdx]);
327
- cutIdx++;
328
- }
329
-
330
- // Fallback: if no user message found in remaining messages, inject a synthetic one
331
- const pruned = cutIdx > 0 ? messages.slice(cutIdx) : messages;
332
- if (pruned.length > 0 && asMsg(pruned[0]).role !== 'user') {
333
- if (DEV) console.log(`[ask] Hard pruned ${cutIdx} messages, injecting synthetic user message (${messages.length} → ${pruned.length + 1})`);
334
- const syntheticUser: UserMessage = {
335
- role: 'user',
336
- content: '[System Note: Older conversation history has been truncated due to context length limits. The user may refer to things you can no longer see. If so, kindly ask them to repeat the context.]',
337
- timestamp: Date.now(),
338
- };
339
- return [syntheticUser as AgentMessage, ...pruned];
340
- } else if (cutIdx > 0 && pruned.length > 0 && asMsg(pruned[0]).role === 'user') {
341
- // If we pruned and the first message IS a user message, prepend the warning to it
342
- const firstMsg = { ...pruned[0] } as UserMessage;
343
- firstMsg.content = `[System Note: Older conversation history has been truncated due to context length limits. The user may refer to things you can no longer see. If so, kindly ask them to repeat the context.]\n\n` + firstMsg.content;
344
- pruned[0] = firstMsg as AgentMessage;
345
- }
346
-
347
- if (cutIdx > 0) {
348
- if (DEV) console.log(`[ask] Hard pruned ${cutIdx} messages (${messages.length} → ${messages.length - cutIdx})`);
349
- return pruned;
350
- }
351
-
352
- return messages;
353
- }
354
-
355
- // ---------------------------------------------------------------------------
356
- // transformContext factory — for Agent's transformContext hook
357
- // ---------------------------------------------------------------------------
358
-
359
- /**
360
- * Create a transformContext function that captures the model and apiKey via closure.
361
- * Agent calls this before each LLM call to manage context window.
362
- */
363
- export function createTransformContext(
364
- systemPrompt: string,
365
- modelName: string,
366
- getCompactModel: () => Model<any>,
367
- apiKey: string,
368
- contextStrategy: string,
369
- ) {
370
- return async (messages: AgentMessage[], signal?: AbortSignal): Promise<AgentMessage[]> => {
371
- // 1. Truncate tool outputs in historical messages
372
- let result = truncateToolOutputs(messages);
373
-
374
- const preTokens = estimateTokens(result);
375
- const sysTokens = estimateStringTokens(systemPrompt);
376
- const ctxLimit = getContextLimit(modelName);
377
- if (DEV) console.log(`[ask] Context: ~${preTokens + sysTokens} tokens (messages=${preTokens}, system=${sysTokens}), limit=${ctxLimit}`);
378
-
379
- // 2. Compact if >70% context limit (skip if user disabled)
380
- if (contextStrategy === 'auto' && needsCompact(result, systemPrompt, modelName)) {
381
- if (DEV) console.log('[ask] Context >70% limit, compacting...');
382
- const compactResult = await compactMessages(
383
- result,
384
- getCompactModel(),
385
- apiKey,
386
- systemPrompt,
387
- modelName,
388
- );
389
- result = compactResult.messages;
390
- if (compactResult.compacted) {
391
- const postTokens = estimateTokens(result);
392
- if (DEV) console.log(`[ask] After compact: ~${postTokens + sysTokens} tokens`);
393
- } else {
394
- if (DEV) console.log('[ask] Compact skipped (too few messages or fallback used), hard prune will handle overflow if needed');
395
- }
396
- }
397
-
398
- // 3. Hard prune if still >90% context limit
399
- result = hardPrune(result, systemPrompt, modelName);
400
-
401
- return result;
402
- };
403
- }