@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,428 +1,50 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import {
3
- truncateToolOutputs,
4
- hardPrune,
5
- estimateTokens,
6
- estimateStringTokens,
7
- getContextLimit,
8
- needsCompact,
9
- compactMessages,
10
- } from '../../lib/agent/context';
11
- import type { AgentMessage } from '@mariozechner/pi-agent-core';
12
- import type { Model } from '@mariozechner/pi-ai';
13
-
14
- // Mock pi-ai's complete() intercept the summarizer call
15
- vi.mock('@mariozechner/pi-ai', async (importOriginal) => {
16
- const actual = await importOriginal<typeof import('@mariozechner/pi-ai')>();
17
- return {
18
- ...actual,
19
- complete: vi.fn(),
20
- };
21
- });
22
-
23
- import { complete } from '@mariozechner/pi-ai';
24
- const mockComplete = vi.mocked(complete);
25
-
26
- // ---------------------------------------------------------------------------
27
- // Helpers — build AgentMessage objects matching pi-ai types
28
- // ---------------------------------------------------------------------------
29
-
30
- const now = Date.now();
31
-
32
- /** Create a UserMessage */
33
- function userMsg(text: string): AgentMessage {
34
- return { role: 'user', content: text, timestamp: now } as AgentMessage;
35
- }
36
-
37
- /** Create an AssistantMessage */
38
- function assistantMsg(text: string): AgentMessage {
39
- return {
40
- role: 'assistant',
41
- content: [{ type: 'text', text }],
42
- api: 'anthropic-messages',
43
- provider: 'anthropic',
44
- model: 'claude-sonnet-4-6',
45
- usage: { inputTokens: 0, outputTokens: 0 },
46
- } as AgentMessage;
47
- }
48
-
49
- /** Create a ToolResultMessage */
50
- function toolResultMsg(toolName: string, text: string): AgentMessage {
51
- return {
52
- role: 'toolResult',
53
- toolCallId: `call_${toolName}`,
54
- toolName,
55
- content: [{ type: 'text', text }],
56
- } as AgentMessage;
57
- }
58
-
59
- /** Create an assistant message with a tool call */
60
- function assistantWithToolCall(toolName: string): AgentMessage {
61
- return {
62
- role: 'assistant',
63
- content: [{ type: 'toolCall', toolCallId: `call_${toolName}`, toolName, args: {} }],
64
- api: 'anthropic-messages',
65
- provider: 'anthropic',
66
- model: 'claude-sonnet-4-6',
67
- usage: { inputTokens: 0, outputTokens: 0 },
68
- } as AgentMessage;
69
- }
70
-
71
- /** Dummy Model (only used as pass-through; complete is mocked) */
72
- const fakeModel = {} as Model<any>;
73
-
74
- // ---------------------------------------------------------------------------
75
- // Token estimation
76
- // ---------------------------------------------------------------------------
77
-
78
- describe('context: token estimation', () => {
79
- it('estimates string tokens as length/4', () => {
80
- expect(estimateStringTokens('abcd')).toBe(1);
81
- expect(estimateStringTokens('abcde')).toBe(2);
82
- expect(estimateStringTokens('')).toBe(0);
83
- });
84
-
85
- it('estimates message tokens for string content', () => {
86
- const msgs = [userMsg('a'.repeat(100))];
87
- expect(estimateTokens(msgs)).toBe(25);
88
- });
89
-
90
- it('returns 0 for messages with no content', () => {
91
- const msgs = [{ role: 'user' as const, content: undefined }] as unknown as AgentMessage[];
92
- expect(estimateTokens(msgs)).toBe(0);
93
- });
94
-
95
- it('estimates tokens for array content (assistant with text)', () => {
96
- const msgs = [assistantMsg('a'.repeat(100))];
97
- expect(estimateTokens(msgs)).toBe(25);
98
- });
99
-
100
- it('estimates tokens for tool call args', () => {
101
- const msgs = [assistantWithToolCall('read_file')];
102
- // args = {} → JSON.stringify = "{}" = 2 chars → ceil(2/4) = 1
103
- expect(estimateTokens(msgs)).toBeGreaterThanOrEqual(1);
104
- });
105
- });
106
-
107
- // ---------------------------------------------------------------------------
108
- // Context limits
109
- // ---------------------------------------------------------------------------
110
-
111
- describe('context: model limits', () => {
112
- it('returns correct limit for known models', () => {
113
- expect(getContextLimit('claude-3.5-sonnet')).toBe(200_000);
114
- expect(getContextLimit('gpt-4o-mini')).toBe(128_000);
115
- expect(getContextLimit('gpt-3.5-turbo')).toBe(16_000);
116
- });
117
-
118
- it('returns default for unknown models', () => {
119
- expect(getContextLimit('llama-3-70b')).toBe(100_000);
120
- });
121
-
122
- it('matches gpt-4o before gpt-4 (prefix sort)', () => {
123
- expect(getContextLimit('gpt-4o-mini')).toBe(128_000);
124
- expect(getContextLimit('gpt-4o-2024-05-13')).toBe(128_000);
125
- expect(getContextLimit('gpt-4-turbo')).toBe(128_000);
126
- });
127
-
128
- it('matches gpt-5 models correctly', () => {
129
- expect(getContextLimit('gpt-5')).toBe(200_000);
130
- expect(getContextLimit('gpt-5.4')).toBe(200_000);
131
- });
132
-
133
- it('is case-insensitive', () => {
134
- expect(getContextLimit('Claude-3.5-Sonnet')).toBe(200_000);
135
- expect(getContextLimit('GPT-4O-MINI')).toBe(128_000);
136
- });
137
-
138
- it('needsCompact detects threshold breach', () => {
139
- const bigMsg = userMsg('x'.repeat(560_001));
140
- expect(needsCompact([bigMsg], '', 'claude-3.5-sonnet')).toBe(true);
141
-
142
- const smallMsg = userMsg('hello');
143
- expect(needsCompact([smallMsg], '', 'claude-3.5-sonnet')).toBe(false);
144
- });
145
- });
146
-
147
- // ---------------------------------------------------------------------------
148
- // truncateToolOutputs
149
- // ---------------------------------------------------------------------------
150
-
151
- describe('context: truncateToolOutputs', () => {
152
- it('truncates long tool outputs in non-last tool messages', () => {
153
- const msgs: AgentMessage[] = [
154
- userMsg('hello'),
155
- toolResultMsg('read_file', 'x'.repeat(5000)),
156
- assistantMsg('done'),
157
- toolResultMsg('read_file', 'y'.repeat(5000)), // last tool — kept intact
158
- ];
159
- const result = truncateToolOutputs(msgs);
160
- // First tool msg (idx 1) should be truncated (read_file limit = 2000)
161
- const firstTool = result[1] as any;
162
- expect(firstTool.content[0].text.length).toBeLessThan(5000);
163
- expect(firstTool.content[0].text).toContain('[...truncated');
164
- // Last tool msg (idx 3) should be untouched
165
- const lastTool = result[3] as any;
166
- expect(lastTool.content[0].text).toBe('y'.repeat(5000));
167
- });
168
-
169
- it('handles empty messages array', () => {
170
- expect(truncateToolOutputs([])).toEqual([]);
171
- });
172
-
173
- it('handles messages with no tool messages', () => {
174
- const msgs = [userMsg('hi'), assistantMsg('hello')];
175
- const result = truncateToolOutputs(msgs);
176
- expect(result).toEqual(msgs);
177
- });
178
-
179
- it('does not truncate short tool outputs', () => {
180
- const msgs: AgentMessage[] = [
181
- toolResultMsg('write_file', 'File written: test.md'),
182
- toolResultMsg('read_file', 'short content'),
183
- ];
184
- const result = truncateToolOutputs(msgs);
185
- // Last tool kept intact, first tool is short enough
186
- expect((result[0] as any).content[0].text).toBe('File written: test.md');
187
- });
188
- });
189
-
190
- // ---------------------------------------------------------------------------
191
- // hardPrune
192
- // ---------------------------------------------------------------------------
193
-
194
- describe('context: hardPrune', () => {
195
- it('returns messages unchanged when under threshold', () => {
196
- const msgs = [userMsg('hello'), assistantMsg('hi')];
197
- const result = hardPrune(msgs, '', 'claude');
198
- expect(result).toBe(msgs); // same reference — no pruning
199
- });
200
-
201
- it('prunes earliest messages when over 90% context', () => {
202
- const msgs: AgentMessage[] = [
203
- userMsg('a'.repeat(400_000)),
204
- assistantMsg('b'.repeat(400_000)),
205
- userMsg('c'.repeat(100)),
206
- assistantMsg('d'.repeat(100)),
207
- ];
208
- const result = hardPrune(msgs, '', 'claude');
209
- expect(result.length).toBeLessThan(msgs.length);
210
- });
211
-
212
- it('ensures first message is user role after pruning', () => {
213
- const msgs: AgentMessage[] = [
214
- userMsg('a'.repeat(100_000)),
215
- assistantMsg('b'.repeat(100_000)),
216
- assistantMsg('c'.repeat(1000)),
217
- userMsg('keep this'),
218
- assistantMsg('and this'),
219
- ];
220
- const result = hardPrune(msgs, '', 'gpt-3.5');
221
- expect(result.length).toBeGreaterThan(0);
222
- expect((result[0] as any).role).toBe('user');
223
- });
224
-
225
- it('skips toolResult messages that are orphaned after cut', () => {
226
- const msgs: AgentMessage[] = [
227
- userMsg('a'.repeat(100_000)),
228
- assistantMsg('b'.repeat(100_000)),
229
- toolResultMsg('read_file', 'x'.repeat(1000)),
230
- userMsg('keep'),
231
- assistantMsg('response'),
232
- ];
233
- const result = hardPrune(msgs, '', 'gpt-3.5');
234
- expect((result[0] as any).role).toBe('user');
235
- expect((result[0] as any).role).not.toBe('toolResult');
236
- });
237
-
238
- it('injects synthetic user message when no user message remains', () => {
239
- const msgs: AgentMessage[] = [
240
- userMsg('a'.repeat(200_000)),
241
- assistantMsg('b'.repeat(1000)),
242
- assistantMsg('c'.repeat(1000)),
243
- ];
244
- const result = hardPrune(msgs, '', 'gpt-3.5');
245
- expect((result[0] as any).role).toBe('user');
246
- const content = (result[0] as any).content;
247
- expect(typeof content === 'string' && content.includes('truncated')).toBe(true);
248
- });
249
-
250
- it('preserves messages when exactly at threshold', () => {
251
- const msgs = [userMsg('small'), assistantMsg('also small')];
252
- const result = hardPrune(msgs, '', 'claude');
253
- expect(result).toBe(msgs);
254
- });
255
- });
256
-
257
- // ---------------------------------------------------------------------------
258
- // compactMessages
259
- // ---------------------------------------------------------------------------
260
-
261
- describe('context: compactMessages', () => {
262
- beforeEach(() => {
263
- mockComplete.mockReset();
264
- });
265
-
266
- it('returns uncompacted when fewer than 6 messages', async () => {
267
- const msgs = [userMsg('a'), assistantMsg('b'), userMsg('c'), assistantMsg('d')];
268
- const result = await compactMessages(msgs, fakeModel, 'test-key', '', 'claude');
269
- expect(result.compacted).toBe(false);
270
- expect(result.messages).toBe(msgs);
271
- expect(mockComplete).not.toHaveBeenCalled();
272
- });
273
-
274
- it('returns uncompacted when early messages < 2', async () => {
275
- const msgs = [
276
- userMsg('only-early'),
277
- assistantMsg('r1'), userMsg('r2'), assistantMsg('r3'),
278
- userMsg('r4'), assistantMsg('r5'), userMsg('r6'),
279
- ];
280
- const result = await compactMessages(msgs, fakeModel, 'test-key', '', 'claude');
281
- expect(result.compacted).toBe(false);
282
- });
283
-
284
- it('prepends summary as separate user message when recentMessages[0] is assistant', async () => {
285
- mockComplete.mockResolvedValue({
286
- role: 'assistant',
287
- content: [{ type: 'text', text: 'Summary of conversation.' }],
288
- api: 'anthropic-messages', provider: 'anthropic', model: 'claude',
289
- usage: { inputTokens: 0, outputTokens: 0 },
290
- } as any);
291
-
292
- const msgs: AgentMessage[] = [
293
- userMsg('early1'),
294
- assistantMsg('early2'),
295
- assistantMsg('recent1'),
296
- userMsg('recent2'),
297
- assistantMsg('recent3'),
298
- userMsg('recent4'),
299
- assistantMsg('recent5'),
300
- userMsg('recent6'),
301
- ];
302
-
303
- const result = await compactMessages(msgs, fakeModel, 'test-key', '', 'claude');
304
- expect(result.compacted).toBe(true);
305
- expect(result.messages.length).toBe(7); // 1 summary + 6 recent
306
- expect((result.messages[0] as any).role).toBe('user');
307
- const content = (result.messages[0] as any).content;
308
- expect(typeof content === 'string').toBe(true);
309
- expect(content).toContain('Summary of conversation.');
310
- expect((result.messages[1] as any).role).toBe('assistant');
311
- });
312
-
313
- it('merges summary into first user message to avoid consecutive user→user', async () => {
314
- mockComplete.mockResolvedValue({
315
- role: 'assistant',
316
- content: [{ type: 'text', text: 'This is the summary.' }],
317
- api: 'anthropic-messages', provider: 'anthropic', model: 'claude',
318
- usage: { inputTokens: 0, outputTokens: 0 },
319
- } as any);
320
-
321
- const msgs: AgentMessage[] = [
322
- userMsg('early1'),
323
- assistantMsg('early2'),
324
- userMsg('recent-user-first'),
325
- assistantMsg('recent2'),
326
- userMsg('recent3'),
327
- assistantMsg('recent4'),
328
- userMsg('recent5'),
329
- assistantMsg('recent6'),
330
- ];
331
-
332
- const result = await compactMessages(msgs, fakeModel, 'test-key', '', 'claude');
333
- expect(result.compacted).toBe(true);
334
- expect(result.messages.length).toBe(6);
335
- expect((result.messages[0] as any).role).toBe('user');
336
- const content = (result.messages[0] as any).content;
337
- expect(content).toContain('This is the summary.');
338
- expect(content).toContain('recent-user-first');
339
- expect((result.messages[1] as any).role).toBe('assistant');
340
- });
341
-
342
- it('handles multimodal (array) content in first user message', async () => {
343
- mockComplete.mockResolvedValue({
344
- role: 'assistant',
345
- content: [{ type: 'text', text: 'Multimodal summary.' }],
346
- api: 'anthropic-messages', provider: 'anthropic', model: 'claude',
347
- usage: { inputTokens: 0, outputTokens: 0 },
348
- } as any);
349
-
350
- const multimodalUser: AgentMessage = {
351
- role: 'user',
352
- content: [
353
- { type: 'image', image: new Uint8Array(0), mimeType: 'image/png' },
354
- { type: 'text', text: 'describe this image' },
355
- ],
356
- timestamp: now,
357
- } as unknown as AgentMessage;
358
-
359
- const msgs: AgentMessage[] = [
360
- userMsg('early1'),
361
- assistantMsg('early2'),
362
- multimodalUser,
363
- assistantMsg('recent2'),
364
- userMsg('recent3'),
365
- assistantMsg('recent4'),
366
- userMsg('recent5'),
367
- assistantMsg('recent6'),
368
- ];
369
-
370
- const result = await compactMessages(msgs, fakeModel, 'test-key', '', 'claude');
371
- expect(result.compacted).toBe(true);
372
- expect(result.messages.length).toBe(6);
373
- expect((result.messages[0] as any).role).toBe('user');
374
- const content = (result.messages[0] as any).content;
375
- expect(Array.isArray(content)).toBe(true);
376
- const parts = content as any[];
377
- expect(parts[0].type).toBe('text');
378
- expect(parts[0].text).toContain('Multimodal summary.');
379
- });
380
-
381
- it('falls back to hardPrune when complete() throws, then throws if pruning insufficient', async () => {
382
- mockComplete.mockRejectedValue(new Error('API error'));
383
-
384
- const msgs: AgentMessage[] = [
385
- userMsg('early1'), assistantMsg('early2'),
386
- userMsg('recent1'), assistantMsg('recent2'),
387
- userMsg('recent3'), assistantMsg('recent4'),
388
- userMsg('recent5'), assistantMsg('recent6'),
389
- ];
390
-
391
- // Small messages on a large model — hardPrune won't help, so it throws
392
- await expect(
393
- compactMessages(msgs, fakeModel, 'test-key', '', 'claude'),
394
- ).rejects.toThrow('Context compaction failed');
395
- });
396
-
397
- it('does not split between assistant tool-call and tool result (M6)', async () => {
398
- mockComplete.mockResolvedValue({
399
- role: 'assistant',
400
- content: [{ type: 'text', text: 'Tool summary.' }],
401
- api: 'anthropic-messages', provider: 'anthropic', model: 'claude',
402
- usage: { inputTokens: 0, outputTokens: 0 },
403
- } as any);
404
-
405
- const msgs: AgentMessage[] = [
406
- userMsg('e1'),
407
- assistantMsg('e2'),
408
- userMsg('e3'),
409
- assistantWithToolCall('read_file'),
410
- toolResultMsg('read_file', 'file content here'),
411
- userMsg('r1'),
412
- assistantMsg('r2'),
413
- userMsg('r3'),
414
- assistantMsg('r4'),
415
- userMsg('r5'),
416
- ];
417
-
418
- const result = await compactMessages(msgs, fakeModel, 'test-key', '', 'claude');
419
- expect(result.compacted).toBe(true);
420
-
421
- for (let i = 0; i < result.messages.length; i++) {
422
- if ((result.messages[i] as any).role === 'toolResult') {
423
- expect(i).toBeGreaterThan(0);
424
- expect((result.messages[i - 1] as any).role).toBe('assistant');
425
- }
426
- }
1
+ import { describe, it, expect } from 'vitest';
2
+ import { estimateStringTokens } from '../../lib/agent/context';
3
+
4
+ describe('Context estimation', () => {
5
+ describe('estimateStringTokens', () => {
6
+ it('estimates ASCII text tokens (~0.25 per char)', () => {
7
+ const text = 'Hello, this is a test sentence with some words.';
8
+ const tokens = estimateStringTokens(text);
9
+ // ~48 chars → ~12 tokens
10
+ expect(tokens).toBeGreaterThan(5);
11
+ expect(tokens).toBeLessThan(25);
12
+ });
13
+
14
+ it('estimates CJK text tokens (~1.5 per char)', () => {
15
+ const text = '你好世界这是测试';
16
+ const tokens = estimateStringTokens(text);
17
+ // 8 CJK chars → ~12 tokens
18
+ expect(tokens).toBeGreaterThan(8);
19
+ expect(tokens).toBeLessThan(20);
20
+ });
21
+
22
+ it('handles mixed CJK and ASCII', () => {
23
+ const text = 'Hello 你好 World 世界';
24
+ const tokens = estimateStringTokens(text);
25
+ expect(tokens).toBeGreaterThan(5);
26
+ expect(tokens).toBeLessThan(20);
27
+ });
28
+
29
+ it('returns 0 for empty string', () => {
30
+ expect(estimateStringTokens('')).toBe(0);
31
+ });
32
+
33
+ it('estimates a typical system prompt size', () => {
34
+ // Simulate a moderate system prompt
35
+ const prompt = 'You are a helpful assistant.\n'.repeat(100);
36
+ const tokens = estimateStringTokens(prompt);
37
+ // ~2800 chars ~700 tokens
38
+ expect(tokens).toBeGreaterThan(500);
39
+ expect(tokens).toBeLessThan(1000);
40
+ });
41
+
42
+ it('estimates large content (20K chars) reasonably', () => {
43
+ const large = 'a'.repeat(20_000);
44
+ const tokens = estimateStringTokens(large);
45
+ // 20000 ASCII chars ~5000 tokens
46
+ expect(tokens).toBeGreaterThanOrEqual(4000);
47
+ expect(tokens).toBeLessThanOrEqual(6000);
48
+ });
427
49
  });
428
50
  });
@@ -38,13 +38,13 @@ describe('pi skill integration', () => {
38
38
  expect(result).toEqual({ name: 'test-skill', description: 'useful helper' });
39
39
  });
40
40
 
41
- it('scans MindOS skill directories with precedence and metadata', () => {
41
+ it('scans MindOS skill directories with precedence and metadata', async () => {
42
42
  writeSkill(path.join(projectRoot, 'app', 'data', 'skills'), 'mindos', '---\nname: mindos\ndescription: builtin\n---\n');
43
43
  writeSkill(path.join(projectRoot, 'skills'), 'project-helper', '---\nname: project-helper\ndescription: project builtin\n---\n');
44
44
  writeSkill(path.join(mindRoot, '.skills'), 'user-helper', '---\nname: user-helper\ndescription: user custom\n---\n');
45
45
  writeSkill(path.join(tempRoot, '.mindos', 'skills'), 'global-helper', '---\nname: global-helper\ndescription: global skill\n---\n');
46
46
 
47
- const skills = scanSkillDirs({ projectRoot, mindRoot, disabledSkills: ['project-helper'] });
47
+ const skills = await scanSkillDirs({ projectRoot, mindRoot, disabledSkills: ['project-helper'] });
48
48
 
49
49
  expect(skills.map((skill) => skill.name)).toEqual(['mindos', 'project-helper', 'user-helper', 'global-helper']);
50
50
  expect(skills.find((skill) => skill.name === 'mindos')).toMatchObject({ source: 'builtin', editable: false, origin: 'app-builtin', enabled: true });
@@ -53,11 +53,11 @@ describe('pi skill integration', () => {
53
53
  expect(skills.find((skill) => skill.name === 'global-helper')).toMatchObject({ source: 'user', editable: true, origin: 'mindos-global', enabled: true });
54
54
  });
55
55
 
56
- it('knowledge base skill takes precedence over global skill with same name', () => {
56
+ it('knowledge base skill takes precedence over global skill with same name', async () => {
57
57
  writeSkill(path.join(mindRoot, '.skills'), 'shared-skill', '---\nname: shared-skill\ndescription: from kb\n---\n');
58
58
  writeSkill(path.join(tempRoot, '.mindos', 'skills'), 'shared-skill', '---\nname: shared-skill\ndescription: from global\n---\n');
59
59
 
60
- const skills = scanSkillDirs({ projectRoot, mindRoot });
60
+ const skills = await scanSkillDirs({ projectRoot, mindRoot });
61
61
  const matched = skills.filter((s) => s.name === 'shared-skill');
62
62
  expect(matched).toHaveLength(1);
63
63
  expect(matched[0].origin).toBe('mindos-user');
@@ -6,8 +6,11 @@ describe('isAiConfiguredForAsk', () => {
6
6
  expect(
7
7
  isAiConfiguredForAsk({
8
8
  ai: {
9
- provider: 'anthropic',
10
- providers: { anthropic: { apiKey: '***set***' }, openai: { apiKey: '' } },
9
+ activeProvider: 'p_anthro01',
10
+ providers: [
11
+ { id: 'p_anthro01', name: 'Anthropic', protocol: 'anthropic', apiKey: 'sk-ant-test', model: 'claude-sonnet-4-6', baseUrl: '' },
12
+ { id: 'p_openai01', name: 'OpenAI', protocol: 'openai', apiKey: '', model: 'gpt-5.4', baseUrl: '' },
13
+ ],
11
14
  },
12
15
  envOverrides: {},
13
16
  }),
@@ -18,8 +21,11 @@ describe('isAiConfiguredForAsk', () => {
18
21
  expect(
19
22
  isAiConfiguredForAsk({
20
23
  ai: {
21
- provider: 'anthropic',
22
- providers: { anthropic: { apiKey: '' }, openai: { apiKey: '' } },
24
+ activeProvider: 'p_anthro01',
25
+ providers: [
26
+ { id: 'p_anthro01', name: 'Anthropic', protocol: 'anthropic', apiKey: '', model: 'claude-sonnet-4-6', baseUrl: '' },
27
+ { id: 'p_openai01', name: 'OpenAI', protocol: 'openai', apiKey: '', model: 'gpt-5.4', baseUrl: '' },
28
+ ],
23
29
  },
24
30
  envOverrides: { ANTHROPIC_API_KEY: true },
25
31
  }),
@@ -30,8 +36,11 @@ describe('isAiConfiguredForAsk', () => {
30
36
  expect(
31
37
  isAiConfiguredForAsk({
32
38
  ai: {
33
- provider: 'anthropic',
34
- providers: { anthropic: { apiKey: '' }, openai: { apiKey: '***set***' } },
39
+ activeProvider: 'p_anthro01',
40
+ providers: [
41
+ { id: 'p_anthro01', name: 'Anthropic', protocol: 'anthropic', apiKey: '', model: 'claude-sonnet-4-6', baseUrl: '' },
42
+ { id: 'p_openai01', name: 'OpenAI', protocol: 'openai', apiKey: 'sk-openai-test', model: 'gpt-5.4', baseUrl: '' },
43
+ ],
35
44
  },
36
45
  envOverrides: {},
37
46
  }),
@@ -42,8 +51,11 @@ describe('isAiConfiguredForAsk', () => {
42
51
  expect(
43
52
  isAiConfiguredForAsk({
44
53
  ai: {
45
- provider: 'openai',
46
- providers: { anthropic: { apiKey: '' }, openai: { apiKey: '***set***' } },
54
+ activeProvider: 'p_openai01',
55
+ providers: [
56
+ { id: 'p_anthro01', name: 'Anthropic', protocol: 'anthropic', apiKey: '', model: 'claude-sonnet-4-6', baseUrl: '' },
57
+ { id: 'p_openai01', name: 'OpenAI', protocol: 'openai', apiKey: 'sk-openai-test', model: 'gpt-5.4', baseUrl: '' },
58
+ ],
47
59
  },
48
60
  envOverrides: {},
49
61
  }),
@@ -54,18 +66,26 @@ describe('isAiConfiguredForAsk', () => {
54
66
  expect(
55
67
  isAiConfiguredForAsk({
56
68
  ai: {
57
- provider: 'openai',
58
- providers: { anthropic: { apiKey: '' }, openai: { apiKey: '' } },
69
+ activeProvider: 'p_openai01',
70
+ providers: [
71
+ { id: 'p_anthro01', name: 'Anthropic', protocol: 'anthropic', apiKey: '', model: 'claude-sonnet-4-6', baseUrl: '' },
72
+ { id: 'p_openai01', name: 'OpenAI', protocol: 'openai', apiKey: '', model: 'gpt-5.4', baseUrl: '' },
73
+ ],
59
74
  },
60
75
  envOverrides: { OPENAI_API_KEY: true },
61
76
  }),
62
77
  ).toBe(true);
63
78
  });
64
79
 
65
- it('treats missing provider as anthropic (error path)', () => {
80
+ it('treats missing provider as first entry fallback (error path)', () => {
66
81
  expect(
67
82
  isAiConfiguredForAsk({
68
- ai: { providers: { anthropic: { apiKey: '' }, openai: { apiKey: '' } } },
83
+ ai: {
84
+ providers: [
85
+ { id: 'p_anthro01', name: 'Anthropic', protocol: 'anthropic', apiKey: '', model: 'claude-sonnet-4-6', baseUrl: '' },
86
+ { id: 'p_openai01', name: 'OpenAI', protocol: 'openai', apiKey: '', model: 'gpt-5.4', baseUrl: '' },
87
+ ],
88
+ },
69
89
  envOverrides: {},
70
90
  }),
71
91
  ).toBe(false);
@@ -23,11 +23,11 @@ export function getTestMindRoot() {
23
23
  vi.mock('@/lib/settings', () => ({
24
24
  readSettings: () => ({
25
25
  ai: {
26
- provider: 'anthropic' as const,
27
- providers: {
28
- anthropic: { apiKey: '', model: 'claude-sonnet-4-6' },
29
- openai: { apiKey: '', model: 'gpt-5.4', baseUrl: '' },
30
- },
26
+ activeProvider: 'p_anthro01',
27
+ providers: [
28
+ { id: 'p_anthro01', name: 'Anthropic', protocol: 'anthropic', apiKey: '', model: 'claude-sonnet-4-6', baseUrl: '' },
29
+ { id: 'p_openai01', name: 'OpenAI', protocol: 'openai', apiKey: '', model: 'gpt-5.4', baseUrl: '' },
30
+ ],
31
31
  },
32
32
  mindRoot: '',
33
33
  }),