cowork-os 0.3.21

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 (526) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1638 -0
  3. package/bin/cowork.js +42 -0
  4. package/build/entitlements.mac.plist +16 -0
  5. package/build/icon.icns +0 -0
  6. package/build/icon.png +0 -0
  7. package/dist/electron/electron/activity/ActivityRepository.js +190 -0
  8. package/dist/electron/electron/agent/browser/browser-service.js +639 -0
  9. package/dist/electron/electron/agent/context-manager.js +225 -0
  10. package/dist/electron/electron/agent/custom-skill-loader.js +566 -0
  11. package/dist/electron/electron/agent/daemon.js +975 -0
  12. package/dist/electron/electron/agent/executor.js +3561 -0
  13. package/dist/electron/electron/agent/llm/anthropic-provider.js +155 -0
  14. package/dist/electron/electron/agent/llm/bedrock-provider.js +202 -0
  15. package/dist/electron/electron/agent/llm/gemini-provider.js +375 -0
  16. package/dist/electron/electron/agent/llm/index.js +34 -0
  17. package/dist/electron/electron/agent/llm/ollama-provider.js +263 -0
  18. package/dist/electron/electron/agent/llm/openai-oauth.js +101 -0
  19. package/dist/electron/electron/agent/llm/openai-provider.js +657 -0
  20. package/dist/electron/electron/agent/llm/openrouter-provider.js +232 -0
  21. package/dist/electron/electron/agent/llm/pricing.js +160 -0
  22. package/dist/electron/electron/agent/llm/provider-factory.js +880 -0
  23. package/dist/electron/electron/agent/llm/types.js +178 -0
  24. package/dist/electron/electron/agent/queue-manager.js +378 -0
  25. package/dist/electron/electron/agent/sandbox/docker-sandbox.js +402 -0
  26. package/dist/electron/electron/agent/sandbox/macos-sandbox.js +407 -0
  27. package/dist/electron/electron/agent/sandbox/runner.js +410 -0
  28. package/dist/electron/electron/agent/sandbox/sandbox-factory.js +228 -0
  29. package/dist/electron/electron/agent/sandbox/security-utils.js +258 -0
  30. package/dist/electron/electron/agent/search/brave-provider.js +119 -0
  31. package/dist/electron/electron/agent/search/google-provider.js +100 -0
  32. package/dist/electron/electron/agent/search/index.js +28 -0
  33. package/dist/electron/electron/agent/search/provider-factory.js +395 -0
  34. package/dist/electron/electron/agent/search/serpapi-provider.js +112 -0
  35. package/dist/electron/electron/agent/search/tavily-provider.js +90 -0
  36. package/dist/electron/electron/agent/search/types.js +40 -0
  37. package/dist/electron/electron/agent/security/index.js +12 -0
  38. package/dist/electron/electron/agent/security/input-sanitizer.js +303 -0
  39. package/dist/electron/electron/agent/security/output-filter.js +217 -0
  40. package/dist/electron/electron/agent/skill-eligibility.js +281 -0
  41. package/dist/electron/electron/agent/skill-registry.js +396 -0
  42. package/dist/electron/electron/agent/skills/document.js +878 -0
  43. package/dist/electron/electron/agent/skills/image-generator.js +225 -0
  44. package/dist/electron/electron/agent/skills/organizer.js +141 -0
  45. package/dist/electron/electron/agent/skills/presentation.js +367 -0
  46. package/dist/electron/electron/agent/skills/spreadsheet.js +165 -0
  47. package/dist/electron/electron/agent/tools/browser-tools.js +523 -0
  48. package/dist/electron/electron/agent/tools/builtin-settings.js +384 -0
  49. package/dist/electron/electron/agent/tools/canvas-tools.js +530 -0
  50. package/dist/electron/electron/agent/tools/cron-tools.js +577 -0
  51. package/dist/electron/electron/agent/tools/edit-tools.js +194 -0
  52. package/dist/electron/electron/agent/tools/file-tools.js +719 -0
  53. package/dist/electron/electron/agent/tools/glob-tools.js +283 -0
  54. package/dist/electron/electron/agent/tools/grep-tools.js +387 -0
  55. package/dist/electron/electron/agent/tools/image-tools.js +111 -0
  56. package/dist/electron/electron/agent/tools/mention-tools.js +282 -0
  57. package/dist/electron/electron/agent/tools/node-tools.js +476 -0
  58. package/dist/electron/electron/agent/tools/registry.js +2719 -0
  59. package/dist/electron/electron/agent/tools/search-tools.js +91 -0
  60. package/dist/electron/electron/agent/tools/shell-tools.js +574 -0
  61. package/dist/electron/electron/agent/tools/skill-tools.js +274 -0
  62. package/dist/electron/electron/agent/tools/system-tools.js +578 -0
  63. package/dist/electron/electron/agent/tools/web-fetch-tools.js +444 -0
  64. package/dist/electron/electron/agent/tools/x-tools.js +264 -0
  65. package/dist/electron/electron/agents/AgentRoleRepository.js +420 -0
  66. package/dist/electron/electron/agents/HeartbeatService.js +356 -0
  67. package/dist/electron/electron/agents/MentionRepository.js +197 -0
  68. package/dist/electron/electron/agents/TaskSubscriptionRepository.js +168 -0
  69. package/dist/electron/electron/agents/WorkingStateRepository.js +229 -0
  70. package/dist/electron/electron/canvas/canvas-manager.js +714 -0
  71. package/dist/electron/electron/canvas/canvas-preload.js +53 -0
  72. package/dist/electron/electron/canvas/canvas-protocol.js +195 -0
  73. package/dist/electron/electron/canvas/canvas-store.js +174 -0
  74. package/dist/electron/electron/canvas/index.js +13 -0
  75. package/dist/electron/electron/control-plane/client.js +364 -0
  76. package/dist/electron/electron/control-plane/handlers.js +572 -0
  77. package/dist/electron/electron/control-plane/index.js +41 -0
  78. package/dist/electron/electron/control-plane/node-manager.js +264 -0
  79. package/dist/electron/electron/control-plane/protocol.js +194 -0
  80. package/dist/electron/electron/control-plane/remote-client.js +437 -0
  81. package/dist/electron/electron/control-plane/server.js +640 -0
  82. package/dist/electron/electron/control-plane/settings.js +369 -0
  83. package/dist/electron/electron/control-plane/ssh-tunnel.js +549 -0
  84. package/dist/electron/electron/cron/index.js +30 -0
  85. package/dist/electron/electron/cron/schedule.js +190 -0
  86. package/dist/electron/electron/cron/service.js +614 -0
  87. package/dist/electron/electron/cron/store.js +155 -0
  88. package/dist/electron/electron/cron/types.js +82 -0
  89. package/dist/electron/electron/cron/webhook.js +258 -0
  90. package/dist/electron/electron/database/SecureSettingsRepository.js +444 -0
  91. package/dist/electron/electron/database/TaskLabelRepository.js +120 -0
  92. package/dist/electron/electron/database/repositories.js +1781 -0
  93. package/dist/electron/electron/database/schema.js +978 -0
  94. package/dist/electron/electron/extensions/index.js +33 -0
  95. package/dist/electron/electron/extensions/loader.js +313 -0
  96. package/dist/electron/electron/extensions/registry.js +485 -0
  97. package/dist/electron/electron/extensions/types.js +11 -0
  98. package/dist/electron/electron/gateway/channel-registry.js +1102 -0
  99. package/dist/electron/electron/gateway/channels/bluebubbles-client.js +479 -0
  100. package/dist/electron/electron/gateway/channels/bluebubbles.js +432 -0
  101. package/dist/electron/electron/gateway/channels/discord.js +975 -0
  102. package/dist/electron/electron/gateway/channels/email-client.js +593 -0
  103. package/dist/electron/electron/gateway/channels/email.js +443 -0
  104. package/dist/electron/electron/gateway/channels/google-chat.js +631 -0
  105. package/dist/electron/electron/gateway/channels/imessage-client.js +363 -0
  106. package/dist/electron/electron/gateway/channels/imessage.js +465 -0
  107. package/dist/electron/electron/gateway/channels/index.js +36 -0
  108. package/dist/electron/electron/gateway/channels/line-client.js +470 -0
  109. package/dist/electron/electron/gateway/channels/line.js +479 -0
  110. package/dist/electron/electron/gateway/channels/matrix-client.js +432 -0
  111. package/dist/electron/electron/gateway/channels/matrix.js +592 -0
  112. package/dist/electron/electron/gateway/channels/mattermost-client.js +394 -0
  113. package/dist/electron/electron/gateway/channels/mattermost.js +496 -0
  114. package/dist/electron/electron/gateway/channels/signal-client.js +500 -0
  115. package/dist/electron/electron/gateway/channels/signal.js +582 -0
  116. package/dist/electron/electron/gateway/channels/slack.js +415 -0
  117. package/dist/electron/electron/gateway/channels/teams.js +596 -0
  118. package/dist/electron/electron/gateway/channels/telegram.js +1390 -0
  119. package/dist/electron/electron/gateway/channels/twitch-client.js +502 -0
  120. package/dist/electron/electron/gateway/channels/twitch.js +396 -0
  121. package/dist/electron/electron/gateway/channels/types.js +8 -0
  122. package/dist/electron/electron/gateway/channels/whatsapp.js +953 -0
  123. package/dist/electron/electron/gateway/context-policy.js +268 -0
  124. package/dist/electron/electron/gateway/index.js +1063 -0
  125. package/dist/electron/electron/gateway/infrastructure.js +496 -0
  126. package/dist/electron/electron/gateway/router.js +2700 -0
  127. package/dist/electron/electron/gateway/security.js +375 -0
  128. package/dist/electron/electron/gateway/session.js +115 -0
  129. package/dist/electron/electron/gateway/tunnel.js +503 -0
  130. package/dist/electron/electron/guardrails/guardrail-manager.js +348 -0
  131. package/dist/electron/electron/hooks/gmail-watcher.js +300 -0
  132. package/dist/electron/electron/hooks/index.js +46 -0
  133. package/dist/electron/electron/hooks/mappings.js +381 -0
  134. package/dist/electron/electron/hooks/server.js +480 -0
  135. package/dist/electron/electron/hooks/settings.js +447 -0
  136. package/dist/electron/electron/hooks/types.js +41 -0
  137. package/dist/electron/electron/ipc/canvas-handlers.js +158 -0
  138. package/dist/electron/electron/ipc/handlers.js +3138 -0
  139. package/dist/electron/electron/ipc/mission-control-handlers.js +141 -0
  140. package/dist/electron/electron/main.js +448 -0
  141. package/dist/electron/electron/mcp/client/MCPClientManager.js +330 -0
  142. package/dist/electron/electron/mcp/client/MCPServerConnection.js +437 -0
  143. package/dist/electron/electron/mcp/client/transports/SSETransport.js +304 -0
  144. package/dist/electron/electron/mcp/client/transports/StdioTransport.js +307 -0
  145. package/dist/electron/electron/mcp/client/transports/WebSocketTransport.js +329 -0
  146. package/dist/electron/electron/mcp/host/MCPHostServer.js +354 -0
  147. package/dist/electron/electron/mcp/host/ToolAdapter.js +100 -0
  148. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +497 -0
  149. package/dist/electron/electron/mcp/settings.js +446 -0
  150. package/dist/electron/electron/mcp/types.js +59 -0
  151. package/dist/electron/electron/memory/MemoryService.js +435 -0
  152. package/dist/electron/electron/notifications/index.js +17 -0
  153. package/dist/electron/electron/notifications/service.js +118 -0
  154. package/dist/electron/electron/notifications/store.js +144 -0
  155. package/dist/electron/electron/preload.js +842 -0
  156. package/dist/electron/electron/reports/StandupReportService.js +272 -0
  157. package/dist/electron/electron/security/concurrency.js +293 -0
  158. package/dist/electron/electron/security/index.js +15 -0
  159. package/dist/electron/electron/security/policy-manager.js +435 -0
  160. package/dist/electron/electron/settings/appearance-manager.js +193 -0
  161. package/dist/electron/electron/settings/personality-manager.js +724 -0
  162. package/dist/electron/electron/settings/x-manager.js +58 -0
  163. package/dist/electron/electron/tailscale/exposure.js +188 -0
  164. package/dist/electron/electron/tailscale/index.js +28 -0
  165. package/dist/electron/electron/tailscale/settings.js +205 -0
  166. package/dist/electron/electron/tailscale/tailscale.js +355 -0
  167. package/dist/electron/electron/tray/QuickInputWindow.js +568 -0
  168. package/dist/electron/electron/tray/TrayManager.js +895 -0
  169. package/dist/electron/electron/tray/index.js +9 -0
  170. package/dist/electron/electron/updater/index.js +6 -0
  171. package/dist/electron/electron/updater/update-manager.js +418 -0
  172. package/dist/electron/electron/utils/env-migration.js +209 -0
  173. package/dist/electron/electron/utils/process.js +102 -0
  174. package/dist/electron/electron/utils/rate-limiter.js +104 -0
  175. package/dist/electron/electron/utils/validation.js +419 -0
  176. package/dist/electron/electron/utils/x-cli.js +177 -0
  177. package/dist/electron/electron/voice/VoiceService.js +507 -0
  178. package/dist/electron/electron/voice/index.js +14 -0
  179. package/dist/electron/electron/voice/voice-settings-manager.js +359 -0
  180. package/dist/electron/shared/channelMessages.js +170 -0
  181. package/dist/electron/shared/types.js +1185 -0
  182. package/package.json +159 -0
  183. package/resources/skills/1password.json +10 -0
  184. package/resources/skills/add-documentation.json +31 -0
  185. package/resources/skills/analyze-csv.json +17 -0
  186. package/resources/skills/apple-notes.json +10 -0
  187. package/resources/skills/apple-reminders.json +10 -0
  188. package/resources/skills/auto-commenter.json +10 -0
  189. package/resources/skills/bear-notes.json +10 -0
  190. package/resources/skills/bird.json +35 -0
  191. package/resources/skills/blogwatcher.json +10 -0
  192. package/resources/skills/blucli.json +10 -0
  193. package/resources/skills/bluebubbles.json +10 -0
  194. package/resources/skills/camsnap.json +10 -0
  195. package/resources/skills/clean-imports.json +18 -0
  196. package/resources/skills/code-review.json +18 -0
  197. package/resources/skills/coding-agent.json +10 -0
  198. package/resources/skills/compare-files.json +23 -0
  199. package/resources/skills/convert-code.json +34 -0
  200. package/resources/skills/create-changelog.json +24 -0
  201. package/resources/skills/debug-error.json +17 -0
  202. package/resources/skills/dependency-check.json +10 -0
  203. package/resources/skills/discord.json +10 -0
  204. package/resources/skills/eightctl.json +10 -0
  205. package/resources/skills/explain-code.json +29 -0
  206. package/resources/skills/extract-todos.json +18 -0
  207. package/resources/skills/food-order.json +10 -0
  208. package/resources/skills/gemini.json +10 -0
  209. package/resources/skills/generate-readme.json +10 -0
  210. package/resources/skills/gifgrep.json +10 -0
  211. package/resources/skills/git-commit.json +10 -0
  212. package/resources/skills/github.json +10 -0
  213. package/resources/skills/gog.json +10 -0
  214. package/resources/skills/goplaces.json +10 -0
  215. package/resources/skills/himalaya.json +10 -0
  216. package/resources/skills/imsg.json +10 -0
  217. package/resources/skills/karpathy-guidelines.json +12 -0
  218. package/resources/skills/last30days.json +26 -0
  219. package/resources/skills/local-places.json +10 -0
  220. package/resources/skills/mcporter.json +10 -0
  221. package/resources/skills/model-usage.json +10 -0
  222. package/resources/skills/nano-banana-pro.json +10 -0
  223. package/resources/skills/nano-pdf.json +10 -0
  224. package/resources/skills/notion.json +10 -0
  225. package/resources/skills/obsidian.json +10 -0
  226. package/resources/skills/openai-image-gen.json +10 -0
  227. package/resources/skills/openai-whisper-api.json +10 -0
  228. package/resources/skills/openai-whisper.json +10 -0
  229. package/resources/skills/openhue.json +10 -0
  230. package/resources/skills/oracle.json +10 -0
  231. package/resources/skills/ordercli.json +10 -0
  232. package/resources/skills/peekaboo.json +10 -0
  233. package/resources/skills/project-structure.json +10 -0
  234. package/resources/skills/proofread.json +17 -0
  235. package/resources/skills/refactor-code.json +31 -0
  236. package/resources/skills/rename-symbol.json +23 -0
  237. package/resources/skills/sag.json +10 -0
  238. package/resources/skills/security-audit.json +18 -0
  239. package/resources/skills/session-logs.json +10 -0
  240. package/resources/skills/sherpa-onnx-tts.json +10 -0
  241. package/resources/skills/skill-creator.json +15 -0
  242. package/resources/skills/skill-hub.json +29 -0
  243. package/resources/skills/slack.json +10 -0
  244. package/resources/skills/songsee.json +10 -0
  245. package/resources/skills/sonoscli.json +10 -0
  246. package/resources/skills/spotify-player.json +10 -0
  247. package/resources/skills/startup-cfo.json +55 -0
  248. package/resources/skills/summarize-folder.json +18 -0
  249. package/resources/skills/summarize.json +10 -0
  250. package/resources/skills/things-mac.json +10 -0
  251. package/resources/skills/tmux.json +10 -0
  252. package/resources/skills/translate.json +36 -0
  253. package/resources/skills/trello.json +10 -0
  254. package/resources/skills/video-frames.json +10 -0
  255. package/resources/skills/voice-call.json +10 -0
  256. package/resources/skills/wacli.json +10 -0
  257. package/resources/skills/weather.json +10 -0
  258. package/resources/skills/write-tests.json +31 -0
  259. package/src/electron/activity/ActivityRepository.ts +238 -0
  260. package/src/electron/agent/browser/browser-service.ts +721 -0
  261. package/src/electron/agent/context-manager.ts +257 -0
  262. package/src/electron/agent/custom-skill-loader.ts +634 -0
  263. package/src/electron/agent/daemon.ts +1097 -0
  264. package/src/electron/agent/executor.ts +4017 -0
  265. package/src/electron/agent/llm/anthropic-provider.ts +175 -0
  266. package/src/electron/agent/llm/bedrock-provider.ts +236 -0
  267. package/src/electron/agent/llm/gemini-provider.ts +422 -0
  268. package/src/electron/agent/llm/index.ts +9 -0
  269. package/src/electron/agent/llm/ollama-provider.ts +347 -0
  270. package/src/electron/agent/llm/openai-oauth.ts +127 -0
  271. package/src/electron/agent/llm/openai-provider.ts +686 -0
  272. package/src/electron/agent/llm/openrouter-provider.ts +273 -0
  273. package/src/electron/agent/llm/pricing.ts +180 -0
  274. package/src/electron/agent/llm/provider-factory.ts +971 -0
  275. package/src/electron/agent/llm/types.ts +291 -0
  276. package/src/electron/agent/queue-manager.ts +408 -0
  277. package/src/electron/agent/sandbox/docker-sandbox.ts +453 -0
  278. package/src/electron/agent/sandbox/macos-sandbox.ts +426 -0
  279. package/src/electron/agent/sandbox/runner.ts +453 -0
  280. package/src/electron/agent/sandbox/sandbox-factory.ts +337 -0
  281. package/src/electron/agent/sandbox/security-utils.ts +251 -0
  282. package/src/electron/agent/search/brave-provider.ts +141 -0
  283. package/src/electron/agent/search/google-provider.ts +131 -0
  284. package/src/electron/agent/search/index.ts +6 -0
  285. package/src/electron/agent/search/provider-factory.ts +450 -0
  286. package/src/electron/agent/search/serpapi-provider.ts +138 -0
  287. package/src/electron/agent/search/tavily-provider.ts +108 -0
  288. package/src/electron/agent/search/types.ts +118 -0
  289. package/src/electron/agent/security/index.ts +20 -0
  290. package/src/electron/agent/security/input-sanitizer.ts +380 -0
  291. package/src/electron/agent/security/output-filter.ts +259 -0
  292. package/src/electron/agent/skill-eligibility.ts +334 -0
  293. package/src/electron/agent/skill-registry.ts +457 -0
  294. package/src/electron/agent/skills/document.ts +1070 -0
  295. package/src/electron/agent/skills/image-generator.ts +272 -0
  296. package/src/electron/agent/skills/organizer.ts +131 -0
  297. package/src/electron/agent/skills/presentation.ts +418 -0
  298. package/src/electron/agent/skills/spreadsheet.ts +166 -0
  299. package/src/electron/agent/tools/browser-tools.ts +546 -0
  300. package/src/electron/agent/tools/builtin-settings.ts +422 -0
  301. package/src/electron/agent/tools/canvas-tools.ts +572 -0
  302. package/src/electron/agent/tools/cron-tools.ts +723 -0
  303. package/src/electron/agent/tools/edit-tools.ts +196 -0
  304. package/src/electron/agent/tools/file-tools.ts +811 -0
  305. package/src/electron/agent/tools/glob-tools.ts +303 -0
  306. package/src/electron/agent/tools/grep-tools.ts +432 -0
  307. package/src/electron/agent/tools/image-tools.ts +126 -0
  308. package/src/electron/agent/tools/mention-tools.ts +371 -0
  309. package/src/electron/agent/tools/node-tools.ts +550 -0
  310. package/src/electron/agent/tools/registry.ts +3052 -0
  311. package/src/electron/agent/tools/search-tools.ts +111 -0
  312. package/src/electron/agent/tools/shell-tools.ts +651 -0
  313. package/src/electron/agent/tools/skill-tools.ts +340 -0
  314. package/src/electron/agent/tools/system-tools.ts +665 -0
  315. package/src/electron/agent/tools/web-fetch-tools.ts +528 -0
  316. package/src/electron/agent/tools/x-tools.ts +267 -0
  317. package/src/electron/agents/AgentRoleRepository.ts +557 -0
  318. package/src/electron/agents/HeartbeatService.ts +469 -0
  319. package/src/electron/agents/MentionRepository.ts +242 -0
  320. package/src/electron/agents/TaskSubscriptionRepository.ts +231 -0
  321. package/src/electron/agents/WorkingStateRepository.ts +278 -0
  322. package/src/electron/canvas/canvas-manager.ts +818 -0
  323. package/src/electron/canvas/canvas-preload.ts +102 -0
  324. package/src/electron/canvas/canvas-protocol.ts +174 -0
  325. package/src/electron/canvas/canvas-store.ts +200 -0
  326. package/src/electron/canvas/index.ts +8 -0
  327. package/src/electron/control-plane/client.ts +527 -0
  328. package/src/electron/control-plane/handlers.ts +723 -0
  329. package/src/electron/control-plane/index.ts +51 -0
  330. package/src/electron/control-plane/node-manager.ts +322 -0
  331. package/src/electron/control-plane/protocol.ts +269 -0
  332. package/src/electron/control-plane/remote-client.ts +517 -0
  333. package/src/electron/control-plane/server.ts +853 -0
  334. package/src/electron/control-plane/settings.ts +401 -0
  335. package/src/electron/control-plane/ssh-tunnel.ts +624 -0
  336. package/src/electron/cron/index.ts +9 -0
  337. package/src/electron/cron/schedule.ts +217 -0
  338. package/src/electron/cron/service.ts +743 -0
  339. package/src/electron/cron/store.ts +165 -0
  340. package/src/electron/cron/types.ts +291 -0
  341. package/src/electron/cron/webhook.ts +303 -0
  342. package/src/electron/database/SecureSettingsRepository.ts +514 -0
  343. package/src/electron/database/TaskLabelRepository.ts +148 -0
  344. package/src/electron/database/repositories.ts +2397 -0
  345. package/src/electron/database/schema.ts +1017 -0
  346. package/src/electron/extensions/index.ts +18 -0
  347. package/src/electron/extensions/loader.ts +336 -0
  348. package/src/electron/extensions/registry.ts +546 -0
  349. package/src/electron/extensions/types.ts +372 -0
  350. package/src/electron/gateway/channel-registry.ts +1267 -0
  351. package/src/electron/gateway/channels/bluebubbles-client.ts +641 -0
  352. package/src/electron/gateway/channels/bluebubbles.ts +509 -0
  353. package/src/electron/gateway/channels/discord.ts +1150 -0
  354. package/src/electron/gateway/channels/email-client.ts +708 -0
  355. package/src/electron/gateway/channels/email.ts +516 -0
  356. package/src/electron/gateway/channels/google-chat.ts +760 -0
  357. package/src/electron/gateway/channels/imessage-client.ts +473 -0
  358. package/src/electron/gateway/channels/imessage.ts +520 -0
  359. package/src/electron/gateway/channels/index.ts +21 -0
  360. package/src/electron/gateway/channels/line-client.ts +598 -0
  361. package/src/electron/gateway/channels/line.ts +559 -0
  362. package/src/electron/gateway/channels/matrix-client.ts +632 -0
  363. package/src/electron/gateway/channels/matrix.ts +655 -0
  364. package/src/electron/gateway/channels/mattermost-client.ts +526 -0
  365. package/src/electron/gateway/channels/mattermost.ts +550 -0
  366. package/src/electron/gateway/channels/signal-client.ts +722 -0
  367. package/src/electron/gateway/channels/signal.ts +666 -0
  368. package/src/electron/gateway/channels/slack.ts +458 -0
  369. package/src/electron/gateway/channels/teams.ts +681 -0
  370. package/src/electron/gateway/channels/telegram.ts +1727 -0
  371. package/src/electron/gateway/channels/twitch-client.ts +665 -0
  372. package/src/electron/gateway/channels/twitch.ts +468 -0
  373. package/src/electron/gateway/channels/types.ts +1002 -0
  374. package/src/electron/gateway/channels/whatsapp.ts +1101 -0
  375. package/src/electron/gateway/context-policy.ts +382 -0
  376. package/src/electron/gateway/index.ts +1274 -0
  377. package/src/electron/gateway/infrastructure.ts +645 -0
  378. package/src/electron/gateway/router.ts +3206 -0
  379. package/src/electron/gateway/security.ts +422 -0
  380. package/src/electron/gateway/session.ts +144 -0
  381. package/src/electron/gateway/tunnel.ts +626 -0
  382. package/src/electron/guardrails/guardrail-manager.ts +380 -0
  383. package/src/electron/hooks/gmail-watcher.ts +355 -0
  384. package/src/electron/hooks/index.ts +30 -0
  385. package/src/electron/hooks/mappings.ts +404 -0
  386. package/src/electron/hooks/server.ts +574 -0
  387. package/src/electron/hooks/settings.ts +466 -0
  388. package/src/electron/hooks/types.ts +245 -0
  389. package/src/electron/ipc/canvas-handlers.ts +223 -0
  390. package/src/electron/ipc/handlers.ts +3661 -0
  391. package/src/electron/ipc/mission-control-handlers.ts +182 -0
  392. package/src/electron/main.ts +496 -0
  393. package/src/electron/mcp/client/MCPClientManager.ts +406 -0
  394. package/src/electron/mcp/client/MCPServerConnection.ts +514 -0
  395. package/src/electron/mcp/client/transports/SSETransport.ts +360 -0
  396. package/src/electron/mcp/client/transports/StdioTransport.ts +355 -0
  397. package/src/electron/mcp/client/transports/WebSocketTransport.ts +384 -0
  398. package/src/electron/mcp/host/MCPHostServer.ts +388 -0
  399. package/src/electron/mcp/host/ToolAdapter.ts +140 -0
  400. package/src/electron/mcp/registry/MCPRegistryManager.ts +565 -0
  401. package/src/electron/mcp/settings.ts +468 -0
  402. package/src/electron/mcp/types.ts +371 -0
  403. package/src/electron/memory/MemoryService.ts +523 -0
  404. package/src/electron/notifications/index.ts +16 -0
  405. package/src/electron/notifications/service.ts +161 -0
  406. package/src/electron/notifications/store.ts +163 -0
  407. package/src/electron/preload.ts +2845 -0
  408. package/src/electron/reports/StandupReportService.ts +356 -0
  409. package/src/electron/security/concurrency.ts +333 -0
  410. package/src/electron/security/index.ts +17 -0
  411. package/src/electron/security/policy-manager.ts +539 -0
  412. package/src/electron/settings/appearance-manager.ts +182 -0
  413. package/src/electron/settings/personality-manager.ts +800 -0
  414. package/src/electron/settings/x-manager.ts +62 -0
  415. package/src/electron/tailscale/exposure.ts +262 -0
  416. package/src/electron/tailscale/index.ts +34 -0
  417. package/src/electron/tailscale/settings.ts +218 -0
  418. package/src/electron/tailscale/tailscale.ts +379 -0
  419. package/src/electron/tray/QuickInputWindow.ts +609 -0
  420. package/src/electron/tray/TrayManager.ts +1005 -0
  421. package/src/electron/tray/index.ts +6 -0
  422. package/src/electron/updater/index.ts +1 -0
  423. package/src/electron/updater/update-manager.ts +447 -0
  424. package/src/electron/utils/env-migration.ts +203 -0
  425. package/src/electron/utils/process.ts +124 -0
  426. package/src/electron/utils/rate-limiter.ts +130 -0
  427. package/src/electron/utils/validation.ts +493 -0
  428. package/src/electron/utils/x-cli.ts +198 -0
  429. package/src/electron/voice/VoiceService.ts +583 -0
  430. package/src/electron/voice/index.ts +9 -0
  431. package/src/electron/voice/voice-settings-manager.ts +403 -0
  432. package/src/renderer/App.tsx +775 -0
  433. package/src/renderer/components/ActivityFeed.tsx +407 -0
  434. package/src/renderer/components/ActivityFeedItem.tsx +285 -0
  435. package/src/renderer/components/AgentRoleCard.tsx +343 -0
  436. package/src/renderer/components/AgentRoleEditor.tsx +805 -0
  437. package/src/renderer/components/AgentSquadSettings.tsx +295 -0
  438. package/src/renderer/components/AgentWorkingStatePanel.tsx +411 -0
  439. package/src/renderer/components/AppearanceSettings.tsx +122 -0
  440. package/src/renderer/components/ApprovalDialog.tsx +100 -0
  441. package/src/renderer/components/BlueBubblesSettings.tsx +505 -0
  442. package/src/renderer/components/BuiltinToolsSettings.tsx +307 -0
  443. package/src/renderer/components/CanvasPreview.tsx +1189 -0
  444. package/src/renderer/components/CommandOutput.tsx +202 -0
  445. package/src/renderer/components/ContextPolicySettings.tsx +523 -0
  446. package/src/renderer/components/ControlPlaneSettings.tsx +1134 -0
  447. package/src/renderer/components/DisclaimerModal.tsx +124 -0
  448. package/src/renderer/components/DiscordSettings.tsx +436 -0
  449. package/src/renderer/components/EmailSettings.tsx +606 -0
  450. package/src/renderer/components/ExtensionsSettings.tsx +542 -0
  451. package/src/renderer/components/FileViewer.tsx +224 -0
  452. package/src/renderer/components/GoogleChatSettings.tsx +535 -0
  453. package/src/renderer/components/GuardrailSettings.tsx +487 -0
  454. package/src/renderer/components/HooksSettings.tsx +581 -0
  455. package/src/renderer/components/ImessageSettings.tsx +484 -0
  456. package/src/renderer/components/LineSettings.tsx +483 -0
  457. package/src/renderer/components/MCPRegistryBrowser.tsx +386 -0
  458. package/src/renderer/components/MCPSettings.tsx +943 -0
  459. package/src/renderer/components/MainContent.tsx +2433 -0
  460. package/src/renderer/components/MatrixSettings.tsx +510 -0
  461. package/src/renderer/components/MattermostSettings.tsx +473 -0
  462. package/src/renderer/components/MemorySettings.tsx +247 -0
  463. package/src/renderer/components/MentionBadge.tsx +87 -0
  464. package/src/renderer/components/MentionInput.tsx +409 -0
  465. package/src/renderer/components/MentionList.tsx +476 -0
  466. package/src/renderer/components/MissionControlPanel.tsx +1995 -0
  467. package/src/renderer/components/NodesSettings.tsx +316 -0
  468. package/src/renderer/components/NotificationPanel.tsx +481 -0
  469. package/src/renderer/components/Onboarding/AwakeningOrb.tsx +44 -0
  470. package/src/renderer/components/Onboarding/Onboarding.tsx +443 -0
  471. package/src/renderer/components/Onboarding/TypewriterText.tsx +102 -0
  472. package/src/renderer/components/Onboarding/index.ts +3 -0
  473. package/src/renderer/components/OnboardingModal.tsx +698 -0
  474. package/src/renderer/components/PairingCodeDisplay.tsx +324 -0
  475. package/src/renderer/components/PersonalitySettings.tsx +597 -0
  476. package/src/renderer/components/QueueSettings.tsx +119 -0
  477. package/src/renderer/components/QuickTaskFAB.tsx +71 -0
  478. package/src/renderer/components/RightPanel.tsx +413 -0
  479. package/src/renderer/components/ScheduledTasksSettings.tsx +1328 -0
  480. package/src/renderer/components/SearchSettings.tsx +328 -0
  481. package/src/renderer/components/Settings.tsx +1504 -0
  482. package/src/renderer/components/Sidebar.tsx +344 -0
  483. package/src/renderer/components/SignalSettings.tsx +673 -0
  484. package/src/renderer/components/SkillHubBrowser.tsx +458 -0
  485. package/src/renderer/components/SkillParameterModal.tsx +185 -0
  486. package/src/renderer/components/SkillsSettings.tsx +451 -0
  487. package/src/renderer/components/SlackSettings.tsx +442 -0
  488. package/src/renderer/components/StandupReportViewer.tsx +614 -0
  489. package/src/renderer/components/TaskBoard.tsx +498 -0
  490. package/src/renderer/components/TaskBoardCard.tsx +357 -0
  491. package/src/renderer/components/TaskBoardColumn.tsx +211 -0
  492. package/src/renderer/components/TaskLabelManager.tsx +472 -0
  493. package/src/renderer/components/TaskQueuePanel.tsx +144 -0
  494. package/src/renderer/components/TaskQuickActions.tsx +492 -0
  495. package/src/renderer/components/TaskTimeline.tsx +216 -0
  496. package/src/renderer/components/TaskView.tsx +162 -0
  497. package/src/renderer/components/TeamsSettings.tsx +518 -0
  498. package/src/renderer/components/TelegramSettings.tsx +421 -0
  499. package/src/renderer/components/Toast.tsx +76 -0
  500. package/src/renderer/components/TraySettings.tsx +189 -0
  501. package/src/renderer/components/TwitchSettings.tsx +511 -0
  502. package/src/renderer/components/UpdateSettings.tsx +295 -0
  503. package/src/renderer/components/VoiceIndicator.tsx +270 -0
  504. package/src/renderer/components/VoiceSettings.tsx +867 -0
  505. package/src/renderer/components/WhatsAppSettings.tsx +721 -0
  506. package/src/renderer/components/WorkingStateEditor.tsx +309 -0
  507. package/src/renderer/components/WorkingStateHistory.tsx +481 -0
  508. package/src/renderer/components/WorkspaceSelector.tsx +150 -0
  509. package/src/renderer/components/XSettings.tsx +311 -0
  510. package/src/renderer/global.d.ts +9 -0
  511. package/src/renderer/hooks/useAgentContext.ts +153 -0
  512. package/src/renderer/hooks/useOnboardingFlow.ts +548 -0
  513. package/src/renderer/hooks/useVoiceInput.ts +268 -0
  514. package/src/renderer/index.html +12 -0
  515. package/src/renderer/main.tsx +10 -0
  516. package/src/renderer/public/cowork-os-logo.png +0 -0
  517. package/src/renderer/quick-input.html +164 -0
  518. package/src/renderer/styles/index.css +14504 -0
  519. package/src/renderer/utils/agentMessages.ts +749 -0
  520. package/src/renderer/utils/voice-directives.ts +169 -0
  521. package/src/shared/channelMessages.ts +213 -0
  522. package/src/shared/types.ts +3608 -0
  523. package/tsconfig.electron.json +26 -0
  524. package/tsconfig.json +26 -0
  525. package/tsconfig.node.json +10 -0
  526. package/vite.config.ts +23 -0
@@ -0,0 +1,971 @@
1
+ import { app, safeStorage } from 'electron';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { getModels as getPiAiModels } from '@mariozechner/pi-ai';
5
+ import {
6
+ LLMProvider,
7
+ LLMProviderConfig,
8
+ LLMProviderType,
9
+ MODELS,
10
+ ModelKey,
11
+ DEFAULT_MODEL,
12
+ } from './types';
13
+ import { AnthropicProvider } from './anthropic-provider';
14
+ import { BedrockProvider } from './bedrock-provider';
15
+ import { OllamaProvider } from './ollama-provider';
16
+ import { GeminiProvider } from './gemini-provider';
17
+ import { OpenRouterProvider } from './openrouter-provider';
18
+ import { OpenAIProvider } from './openai-provider';
19
+ import { SecureSettingsRepository } from '../../database/SecureSettingsRepository';
20
+
21
+ const LEGACY_SETTINGS_FILE = 'llm-settings.json';
22
+ const MASKED_VALUE = '***configured***';
23
+ const ENCRYPTED_PREFIX = 'encrypted:';
24
+
25
+ // ============ Legacy Encryption Functions (for migration only) ============
26
+ // These functions are only used to decrypt settings from legacy JSON files
27
+ // during migration to the encrypted database. New settings use full-object
28
+ // encryption via SecureSettingsRepository.
29
+
30
+ /**
31
+ * @deprecated Used only for migration from legacy JSON files
32
+ * Encrypt a secret using OS keychain via safeStorage
33
+ */
34
+ function encryptSecret(value?: string): string | undefined {
35
+ if (!value || !value.trim()) return undefined;
36
+ const trimmed = value.trim();
37
+ if (trimmed === MASKED_VALUE) return undefined;
38
+
39
+ try {
40
+ if (safeStorage.isEncryptionAvailable()) {
41
+ const encrypted = safeStorage.encryptString(trimmed);
42
+ return ENCRYPTED_PREFIX + encrypted.toString('base64');
43
+ }
44
+ } catch (error) {
45
+ console.warn('Failed to encrypt secret, storing masked:', error);
46
+ }
47
+ // Fallback to masked value if encryption fails
48
+ return MASKED_VALUE;
49
+ }
50
+
51
+ /**
52
+ * @deprecated Used only for migration from legacy JSON files
53
+ * Decrypt a secret that was encrypted with safeStorage
54
+ */
55
+ function decryptSecret(value?: string): string | undefined {
56
+ if (!value) return undefined;
57
+ if (value === MASKED_VALUE) return undefined;
58
+
59
+ if (value.startsWith(ENCRYPTED_PREFIX)) {
60
+ try {
61
+ const isAvailable = safeStorage.isEncryptionAvailable();
62
+ if (isAvailable) {
63
+ const encrypted = Buffer.from(value.slice(ENCRYPTED_PREFIX.length), 'base64');
64
+ const decrypted = safeStorage.decryptString(encrypted);
65
+ return decrypted;
66
+ } else {
67
+ console.error('[LLM Settings] safeStorage encryption not available - cannot decrypt secrets');
68
+ console.error('[LLM Settings] You may need to re-enter your API credentials in Settings');
69
+ }
70
+ } catch (error: any) {
71
+ // This can happen after app updates when the code signature changes
72
+ // The macOS Keychain ties encryption to the app's signature
73
+ console.error('[LLM Settings] Failed to decrypt secret - this can happen after app updates');
74
+ console.error('[LLM Settings] Error:', error.message || error);
75
+ console.error('[LLM Settings] Please re-enter your API credentials in Settings');
76
+ }
77
+ }
78
+
79
+ // If not encrypted and not masked, return as-is (for backwards compatibility)
80
+ if (value !== MASKED_VALUE && !value.startsWith(ENCRYPTED_PREFIX)) {
81
+ return value.trim() || undefined;
82
+ }
83
+
84
+ return undefined;
85
+ }
86
+
87
+ /**
88
+ * Normalize a secret value, filtering out masked/encrypted values
89
+ */
90
+ function normalizeSecret(value?: string): string | undefined {
91
+ if (!value) return undefined;
92
+ const trimmed = value.trim();
93
+ if (!trimmed || trimmed === MASKED_VALUE || trimmed.startsWith(ENCRYPTED_PREFIX)) return undefined;
94
+ return trimmed;
95
+ }
96
+
97
+ /**
98
+ * @deprecated Used only for migration from legacy JSON files
99
+ * Decrypt all secrets in legacy settings
100
+ */
101
+ function sanitizeSettings(settings: LLMSettings): LLMSettings {
102
+ const sanitized: LLMSettings = { ...settings };
103
+
104
+ // Decrypt secrets when loading from disk
105
+ if (sanitized.anthropic) {
106
+ sanitized.anthropic = {
107
+ ...sanitized.anthropic,
108
+ apiKey: decryptSecret(sanitized.anthropic.apiKey),
109
+ };
110
+ }
111
+
112
+ if (sanitized.bedrock) {
113
+ sanitized.bedrock = {
114
+ ...sanitized.bedrock,
115
+ secretAccessKey: decryptSecret(sanitized.bedrock.secretAccessKey),
116
+ };
117
+ }
118
+
119
+ if (sanitized.ollama) {
120
+ sanitized.ollama = {
121
+ ...sanitized.ollama,
122
+ apiKey: decryptSecret(sanitized.ollama.apiKey),
123
+ };
124
+ }
125
+
126
+ if (sanitized.gemini) {
127
+ sanitized.gemini = {
128
+ ...sanitized.gemini,
129
+ apiKey: decryptSecret(sanitized.gemini.apiKey),
130
+ };
131
+ }
132
+
133
+ if (sanitized.openrouter) {
134
+ sanitized.openrouter = {
135
+ ...sanitized.openrouter,
136
+ apiKey: decryptSecret(sanitized.openrouter.apiKey),
137
+ };
138
+ }
139
+
140
+ if (sanitized.openai) {
141
+ const decryptedAccessToken = decryptSecret(sanitized.openai.accessToken);
142
+ const decryptedRefreshToken = decryptSecret(sanitized.openai.refreshToken);
143
+
144
+ // Log OAuth token status for debugging
145
+ if (sanitized.openai.authMethod === 'oauth') {
146
+ console.log('[LLM Settings] Loading OpenAI OAuth settings:');
147
+ console.log('[LLM Settings] authMethod:', sanitized.openai.authMethod);
148
+ console.log('[LLM Settings] hasAccessToken:', !!sanitized.openai.accessToken);
149
+ console.log('[LLM Settings] decryptedAccessToken:', !!decryptedAccessToken);
150
+ console.log('[LLM Settings] hasRefreshToken:', !!sanitized.openai.refreshToken);
151
+ console.log('[LLM Settings] decryptedRefreshToken:', !!decryptedRefreshToken);
152
+ }
153
+
154
+ sanitized.openai = {
155
+ ...sanitized.openai,
156
+ apiKey: decryptSecret(sanitized.openai.apiKey),
157
+ accessToken: decryptedAccessToken,
158
+ refreshToken: decryptedRefreshToken,
159
+ };
160
+ }
161
+
162
+ return sanitized;
163
+ }
164
+
165
+ /**
166
+ * Cached model info for dynamic providers
167
+ */
168
+ export interface CachedModelInfo {
169
+ key: string;
170
+ displayName: string;
171
+ description: string;
172
+ // Additional fields for provider-specific info
173
+ contextLength?: number; // For OpenRouter models
174
+ size?: number; // For Ollama models (in bytes)
175
+ }
176
+
177
+ /**
178
+ * Stored settings for LLM provider
179
+ */
180
+ export interface LLMSettings {
181
+ providerType: LLMProviderType;
182
+ modelKey: ModelKey | string; // String for custom Ollama model names
183
+ anthropic?: {
184
+ apiKey?: string;
185
+ };
186
+ bedrock?: {
187
+ region?: string;
188
+ accessKeyId?: string;
189
+ secretAccessKey?: string;
190
+ sessionToken?: string;
191
+ profile?: string;
192
+ useDefaultCredentials?: boolean;
193
+ model?: string;
194
+ };
195
+ ollama?: {
196
+ baseUrl?: string;
197
+ model?: string;
198
+ apiKey?: string; // Optional, for remote Ollama servers
199
+ };
200
+ gemini?: {
201
+ apiKey?: string;
202
+ model?: string;
203
+ };
204
+ openrouter?: {
205
+ apiKey?: string;
206
+ model?: string;
207
+ };
208
+ openai?: {
209
+ apiKey?: string;
210
+ model?: string;
211
+ // OAuth tokens (alternative to API key)
212
+ accessToken?: string;
213
+ refreshToken?: string;
214
+ tokenExpiresAt?: number;
215
+ authMethod?: 'api_key' | 'oauth';
216
+ };
217
+ // Cached models from API (populated when user refreshes)
218
+ cachedGeminiModels?: CachedModelInfo[];
219
+ cachedOpenRouterModels?: CachedModelInfo[];
220
+ cachedOllamaModels?: CachedModelInfo[];
221
+ cachedBedrockModels?: CachedModelInfo[];
222
+ cachedOpenAIModels?: CachedModelInfo[];
223
+ }
224
+
225
+ const DEFAULT_SETTINGS: LLMSettings = {
226
+ providerType: 'anthropic',
227
+ modelKey: DEFAULT_MODEL,
228
+ };
229
+
230
+ /**
231
+ * Factory for creating LLM providers
232
+ */
233
+ export class LLMProviderFactory {
234
+ private static legacySettingsPath: string;
235
+ private static cachedSettings: LLMSettings | null = null;
236
+ private static migrationCompleted = false;
237
+
238
+ /**
239
+ * Initialize the factory
240
+ */
241
+ static initialize(): void {
242
+ const userDataPath = app.getPath('userData');
243
+ this.legacySettingsPath = path.join(userDataPath, LEGACY_SETTINGS_FILE);
244
+
245
+ // Migrate from legacy JSON file to encrypted database
246
+ this.migrateFromLegacyFile();
247
+ }
248
+
249
+ /**
250
+ * Migrate settings from legacy JSON file to encrypted database
251
+ */
252
+ private static migrateFromLegacyFile(): void {
253
+ if (this.migrationCompleted) return;
254
+
255
+ try {
256
+ // Check if SecureSettingsRepository is initialized
257
+ if (!SecureSettingsRepository.isInitialized()) {
258
+ console.log('[LLMProviderFactory] SecureSettingsRepository not yet initialized, skipping migration');
259
+ return;
260
+ }
261
+
262
+ const repository = SecureSettingsRepository.getInstance();
263
+
264
+ // Check if already migrated to database
265
+ if (repository.exists('llm')) {
266
+ this.migrationCompleted = true;
267
+ return;
268
+ }
269
+
270
+ // Check if legacy file exists
271
+ if (!fs.existsSync(this.legacySettingsPath)) {
272
+ console.log('[LLMProviderFactory] No legacy settings file found');
273
+ this.migrationCompleted = true;
274
+ return;
275
+ }
276
+
277
+ console.log('[LLMProviderFactory] Migrating settings from legacy JSON file to encrypted database...');
278
+
279
+ // Create backup before migration
280
+ const backupPath = this.legacySettingsPath + '.migration-backup';
281
+ fs.copyFileSync(this.legacySettingsPath, backupPath);
282
+
283
+ try {
284
+ // Read and decrypt legacy settings
285
+ const data = fs.readFileSync(this.legacySettingsPath, 'utf-8');
286
+ const legacySettings = { ...DEFAULT_SETTINGS, ...JSON.parse(data) };
287
+ const decryptedSettings = sanitizeSettings(legacySettings);
288
+
289
+ // Save to encrypted database
290
+ repository.save('llm', decryptedSettings);
291
+ console.log('[LLMProviderFactory] Settings migrated to encrypted database');
292
+
293
+ // Migration successful - delete backup and original
294
+ fs.unlinkSync(backupPath);
295
+ fs.unlinkSync(this.legacySettingsPath);
296
+ console.log('[LLMProviderFactory] Migration complete, cleaned up legacy files');
297
+
298
+ this.migrationCompleted = true;
299
+ } catch (migrationError) {
300
+ console.error('[LLMProviderFactory] Migration failed, backup preserved at:', backupPath);
301
+ throw migrationError;
302
+ }
303
+ } catch (error) {
304
+ console.error('[LLMProviderFactory] Migration failed:', error);
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Get the path to legacy settings file (for testing)
310
+ */
311
+ static getSettingsPath(): string {
312
+ return this.legacySettingsPath;
313
+ }
314
+
315
+ /**
316
+ * Load settings from encrypted database
317
+ */
318
+ static loadSettings(): LLMSettings {
319
+ if (this.cachedSettings) {
320
+ return this.cachedSettings;
321
+ }
322
+
323
+ let settings: LLMSettings = { ...DEFAULT_SETTINGS };
324
+ let settingsExist = false;
325
+
326
+ try {
327
+ // Try to load from encrypted database
328
+ if (SecureSettingsRepository.isInitialized()) {
329
+ const repository = SecureSettingsRepository.getInstance();
330
+ const stored = repository.load<LLMSettings>('llm');
331
+ if (stored) {
332
+ settings = { ...DEFAULT_SETTINGS, ...stored };
333
+ settingsExist = true;
334
+ }
335
+ }
336
+ } catch (error) {
337
+ console.error('[LLMProviderFactory] Failed to load settings from database:', error);
338
+ }
339
+
340
+ // Auto-detect provider if no settings exist
341
+ if (!settingsExist) {
342
+ const detectedProvider = this.detectProviderFromSettings(settings);
343
+ if (detectedProvider) {
344
+ settings.providerType = detectedProvider;
345
+ console.log(`[LLMProviderFactory] Auto-detected LLM provider: ${detectedProvider}`);
346
+ }
347
+ }
348
+
349
+ this.cachedSettings = settings;
350
+ return settings;
351
+ }
352
+
353
+ /**
354
+ * Detect which provider to use based on saved settings
355
+ * Note: Environment variables are no longer used for security reasons.
356
+ * All configuration should be done through the Settings UI.
357
+ */
358
+ private static detectProviderFromSettings(settings: LLMSettings): LLMProviderType | null {
359
+ // Check if any provider has credentials configured in settings
360
+ if (settings.anthropic?.apiKey) {
361
+ return 'anthropic';
362
+ }
363
+ if (settings.gemini?.apiKey) {
364
+ return 'gemini';
365
+ }
366
+ if (settings.openrouter?.apiKey) {
367
+ return 'openrouter';
368
+ }
369
+ if (settings.openai?.apiKey || settings.openai?.accessToken) {
370
+ return 'openai';
371
+ }
372
+ if (settings.bedrock?.accessKeyId || settings.bedrock?.profile) {
373
+ return 'bedrock';
374
+ }
375
+ if (settings.ollama?.baseUrl || settings.ollama?.model) {
376
+ return 'ollama';
377
+ }
378
+
379
+ // No valid credentials detected - user needs to configure via Settings
380
+ return null;
381
+ }
382
+
383
+ /**
384
+ * Save settings to encrypted database
385
+ */
386
+ static saveSettings(settings: LLMSettings): void {
387
+ try {
388
+ if (!SecureSettingsRepository.isInitialized()) {
389
+ throw new Error('SecureSettingsRepository not initialized');
390
+ }
391
+
392
+ const repository = SecureSettingsRepository.getInstance();
393
+
394
+ // Save entire settings object to encrypted database
395
+ // No need for per-field encryption - the entire object is encrypted
396
+ repository.save('llm', settings);
397
+ this.cachedSettings = settings;
398
+
399
+ console.log('[LLMProviderFactory] Settings saved to encrypted database');
400
+ } catch (error) {
401
+ console.error('[LLMProviderFactory] Failed to save settings:', error);
402
+ throw error;
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Clear cached settings
408
+ */
409
+ static clearCache(): void {
410
+ this.cachedSettings = null;
411
+ }
412
+
413
+ /**
414
+ * Create a provider based on current settings
415
+ * Note: All credentials must be configured via the Settings UI.
416
+ * Environment variables are no longer used for security reasons.
417
+ */
418
+ static createProvider(overrideConfig?: Partial<LLMProviderConfig>): LLMProvider {
419
+ const settings = this.loadSettings();
420
+ const providerType = overrideConfig?.type || settings.providerType;
421
+
422
+ const config: LLMProviderConfig = {
423
+ type: providerType,
424
+ model: this.getModelId(settings.modelKey, providerType, settings.ollama?.model, settings.gemini?.model, settings.openrouter?.model, settings.openai?.model),
425
+ // Anthropic config - from settings only
426
+ anthropicApiKey: normalizeSecret(overrideConfig?.anthropicApiKey) || settings.anthropic?.apiKey,
427
+ // Bedrock config - from settings only
428
+ awsRegion: overrideConfig?.awsRegion || settings.bedrock?.region || 'us-east-1',
429
+ awsAccessKeyId: overrideConfig?.awsAccessKeyId || settings.bedrock?.accessKeyId,
430
+ awsSecretAccessKey: normalizeSecret(overrideConfig?.awsSecretAccessKey) || settings.bedrock?.secretAccessKey,
431
+ awsSessionToken: overrideConfig?.awsSessionToken || settings.bedrock?.sessionToken,
432
+ awsProfile: overrideConfig?.awsProfile || settings.bedrock?.profile,
433
+ // Ollama config - from settings only
434
+ ollamaBaseUrl: overrideConfig?.ollamaBaseUrl || settings.ollama?.baseUrl || 'http://localhost:11434',
435
+ ollamaApiKey: normalizeSecret(overrideConfig?.ollamaApiKey) || settings.ollama?.apiKey,
436
+ // Gemini config - from settings only
437
+ geminiApiKey: normalizeSecret(overrideConfig?.geminiApiKey) || settings.gemini?.apiKey,
438
+ // OpenRouter config - from settings only
439
+ openrouterApiKey: normalizeSecret(overrideConfig?.openrouterApiKey) || settings.openrouter?.apiKey,
440
+ // OpenAI config - from settings only
441
+ openaiApiKey: normalizeSecret(overrideConfig?.openaiApiKey) || settings.openai?.apiKey,
442
+ openaiAccessToken: normalizeSecret(overrideConfig?.openaiAccessToken) || settings.openai?.accessToken,
443
+ openaiRefreshToken: settings.openai?.refreshToken,
444
+ openaiTokenExpiresAt: settings.openai?.tokenExpiresAt,
445
+ };
446
+
447
+ return this.createProviderFromConfig(config);
448
+ }
449
+
450
+ /**
451
+ * Create a provider from explicit config
452
+ */
453
+ static createProviderFromConfig(config: LLMProviderConfig): LLMProvider {
454
+ switch (config.type) {
455
+ case 'anthropic':
456
+ return new AnthropicProvider(config);
457
+ case 'bedrock':
458
+ return new BedrockProvider(config);
459
+ case 'ollama':
460
+ return new OllamaProvider(config);
461
+ case 'gemini':
462
+ return new GeminiProvider(config);
463
+ case 'openrouter':
464
+ return new OpenRouterProvider(config);
465
+ case 'openai':
466
+ return new OpenAIProvider(config);
467
+ default:
468
+ throw new Error(`Unknown provider type: ${config.type}`);
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Get the model ID for a provider
474
+ */
475
+ static getModelId(modelKey: ModelKey | string, providerType: LLMProviderType, ollamaModel?: string, geminiModel?: string, openrouterModel?: string, openaiModel?: string): string {
476
+ // For Ollama, use the specific Ollama model if provided
477
+ if (providerType === 'ollama') {
478
+ return ollamaModel || 'gpt-oss:20b';
479
+ }
480
+
481
+ // For Gemini, use the specific Gemini model if provided or default
482
+ if (providerType === 'gemini') {
483
+ return geminiModel || 'gemini-2.0-flash';
484
+ }
485
+
486
+ // For OpenRouter, use the specific model if provided or default
487
+ if (providerType === 'openrouter') {
488
+ return openrouterModel || 'anthropic/claude-3.5-sonnet';
489
+ }
490
+
491
+ // For OpenAI, use the specific model if provided or default
492
+ if (providerType === 'openai') {
493
+ return openaiModel || 'gpt-4o-mini';
494
+ }
495
+
496
+ // For other providers, look up in MODELS
497
+ const model = MODELS[modelKey as ModelKey];
498
+ if (!model) {
499
+ throw new Error(`Unknown model: ${modelKey}`);
500
+ }
501
+ return model[providerType as 'anthropic' | 'bedrock'];
502
+ }
503
+
504
+ /**
505
+ * Get display name for a model
506
+ */
507
+ static getModelDisplayName(modelKey: ModelKey): string {
508
+ return MODELS[modelKey]?.displayName || modelKey;
509
+ }
510
+
511
+ /**
512
+ * Get all available models
513
+ */
514
+ static getAvailableModels(): Array<{ key: ModelKey; displayName: string }> {
515
+ return Object.entries(MODELS).map(([key, value]) => ({
516
+ key: key as ModelKey,
517
+ displayName: value.displayName,
518
+ }));
519
+ }
520
+
521
+ /**
522
+ * Get available providers based on saved settings configuration
523
+ * Note: Environment variables are no longer checked for security reasons.
524
+ */
525
+ static getAvailableProviders(): Array<{
526
+ type: LLMProviderType;
527
+ name: string;
528
+ configured: boolean;
529
+ }> {
530
+ const settings = this.loadSettings();
531
+
532
+ return [
533
+ {
534
+ type: 'anthropic' as LLMProviderType,
535
+ name: 'Anthropic API',
536
+ configured: !!settings.anthropic?.apiKey,
537
+ },
538
+ {
539
+ type: 'gemini' as LLMProviderType,
540
+ name: 'Google Gemini',
541
+ configured: !!settings.gemini?.apiKey,
542
+ },
543
+ {
544
+ type: 'openrouter' as LLMProviderType,
545
+ name: 'OpenRouter',
546
+ configured: !!settings.openrouter?.apiKey,
547
+ },
548
+ {
549
+ type: 'openai' as LLMProviderType,
550
+ name: 'OpenAI',
551
+ configured: !!(settings.openai?.apiKey || settings.openai?.accessToken),
552
+ },
553
+ {
554
+ type: 'bedrock' as LLMProviderType,
555
+ name: 'AWS Bedrock',
556
+ configured: !!(settings.bedrock?.accessKeyId || settings.bedrock?.profile),
557
+ },
558
+ {
559
+ type: 'ollama' as LLMProviderType,
560
+ name: 'Ollama (Local)',
561
+ configured: !!(settings.ollama?.baseUrl || settings.ollama?.model),
562
+ },
563
+ ];
564
+ }
565
+
566
+ /**
567
+ * Get current configuration status
568
+ */
569
+ static getConfigStatus(): {
570
+ currentProvider: LLMProviderType;
571
+ currentModel: ModelKey | string;
572
+ providers: Array<{ type: LLMProviderType; name: string; configured: boolean }>;
573
+ models: Array<{ key: ModelKey; displayName: string }>;
574
+ } {
575
+ const settings = this.loadSettings();
576
+ return {
577
+ currentProvider: settings.providerType,
578
+ currentModel: settings.modelKey,
579
+ providers: this.getAvailableProviders(),
580
+ models: this.getAvailableModels(),
581
+ };
582
+ }
583
+
584
+ /**
585
+ * Get the currently selected provider type
586
+ */
587
+ static getSelectedProvider(): LLMProviderType {
588
+ const settings = this.loadSettings();
589
+ return settings.providerType;
590
+ }
591
+
592
+ /**
593
+ * Get the currently selected model key
594
+ */
595
+ static getSelectedModel(): ModelKey | string {
596
+ const settings = this.loadSettings();
597
+ return settings.modelKey;
598
+ }
599
+
600
+ /**
601
+ * Get the current LLM settings
602
+ */
603
+ static getSettings(): LLMSettings {
604
+ return this.loadSettings();
605
+ }
606
+
607
+ /**
608
+ * Test a provider configuration
609
+ */
610
+ static async testProvider(config: LLMProviderConfig): Promise<{ success: boolean; error?: string }> {
611
+ try {
612
+ const provider = this.createProviderFromConfig(config);
613
+ return await provider.testConnection();
614
+ } catch (error: any) {
615
+ return {
616
+ success: false,
617
+ error: error.message || 'Failed to create provider',
618
+ };
619
+ }
620
+ }
621
+
622
+ /**
623
+ * Fetch available Bedrock models from AWS
624
+ */
625
+ static async getBedrockModels(config?: {
626
+ region?: string;
627
+ accessKeyId?: string;
628
+ secretAccessKey?: string;
629
+ profile?: string;
630
+ }): Promise<Array<{ id: string; name: string; provider: string; description: string }>> {
631
+ const settings = this.loadSettings();
632
+ const region = config?.region || settings.bedrock?.region || 'us-east-1';
633
+ const accessKeyId = config?.accessKeyId || settings.bedrock?.accessKeyId;
634
+ const secretAccessKey = config?.secretAccessKey || settings.bedrock?.secretAccessKey;
635
+ const profile = config?.profile || settings.bedrock?.profile;
636
+
637
+ // Default Claude models available on Bedrock
638
+ const defaultModels = Object.entries(MODELS).map(([key, value]) => ({
639
+ id: value.bedrock,
640
+ name: value.displayName,
641
+ provider: 'Anthropic',
642
+ description: key.includes('opus') ? 'Most capable for complex tasks' :
643
+ key.includes('sonnet') ? 'Balanced performance and speed' :
644
+ 'Fast and efficient',
645
+ }));
646
+
647
+ try {
648
+ // Import BedrockClient for listing models (different from runtime client)
649
+ const { BedrockClient, ListFoundationModelsCommand } = await import('@aws-sdk/client-bedrock');
650
+ const { fromIni } = await import('@aws-sdk/credential-provider-ini');
651
+
652
+ const clientConfig: any = { region };
653
+
654
+ if (accessKeyId && secretAccessKey) {
655
+ clientConfig.credentials = {
656
+ accessKeyId,
657
+ secretAccessKey,
658
+ };
659
+ } else if (profile) {
660
+ clientConfig.credentials = fromIni({ profile });
661
+ }
662
+
663
+ const client = new BedrockClient(clientConfig);
664
+ const command = new ListFoundationModelsCommand({
665
+ byOutputModality: 'TEXT',
666
+ });
667
+
668
+ const response = await client.send(command);
669
+ const models = response.modelSummaries || [];
670
+
671
+ // Filter for Claude models and format the response
672
+ const claudeModels = models
673
+ .filter((m: any) => m.providerName === 'Anthropic' && m.modelId?.includes('claude'))
674
+ .map((m: any) => ({
675
+ id: m.modelId || '',
676
+ name: m.modelName || m.modelId || '',
677
+ provider: m.providerName || 'Anthropic',
678
+ description: m.modelId?.includes('opus') ? 'Most capable for complex tasks' :
679
+ m.modelId?.includes('sonnet') ? 'Balanced performance and speed' :
680
+ m.modelId?.includes('haiku') ? 'Fast and efficient' :
681
+ 'Claude model',
682
+ }))
683
+ .filter((m: any) => m.id);
684
+
685
+ return claudeModels.length > 0 ? claudeModels : defaultModels;
686
+ } catch (error: any) {
687
+ console.error('Failed to fetch Bedrock models:', error);
688
+ // Return default models on error
689
+ return defaultModels;
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Fetch available Ollama models from the server
695
+ */
696
+ static async getOllamaModels(baseUrl?: string): Promise<Array<{ name: string; size: number; modified: string }>> {
697
+ const settings = this.loadSettings();
698
+ const url = baseUrl || settings.ollama?.baseUrl || 'http://localhost:11434';
699
+
700
+ try {
701
+ console.log(`[ProviderFactory] Fetching Ollama models from ${url}...`);
702
+ const provider = new OllamaProvider({
703
+ type: 'ollama',
704
+ model: '',
705
+ ollamaBaseUrl: url,
706
+ ollamaApiKey: settings.ollama?.apiKey,
707
+ });
708
+ const models = await provider.getAvailableModels();
709
+ console.log(`[ProviderFactory] Fetched ${models.length} models from Ollama`);
710
+ return models;
711
+ } catch (error: any) {
712
+ console.error('Failed to fetch Ollama models:', error);
713
+ return [];
714
+ }
715
+ }
716
+
717
+ /**
718
+ * Fetch available Gemini models from the API
719
+ */
720
+ static async getGeminiModels(apiKey?: string): Promise<Array<{ name: string; displayName: string; description: string }>> {
721
+ const settings = this.loadSettings();
722
+ // Normalize empty strings to undefined
723
+ const normalizedApiKey = apiKey?.trim() || undefined;
724
+ const settingsKey = settings.gemini?.apiKey;
725
+ const key = normalizedApiKey || settingsKey;
726
+
727
+ const defaultModels = [
728
+ { name: 'gemini-2.5-pro-preview-05-06', displayName: 'Gemini 2.5 Pro', description: 'Most capable model for complex tasks' },
729
+ { name: 'gemini-2.5-flash-preview-05-20', displayName: 'Gemini 2.5 Flash', description: 'Fast and efficient for most tasks' },
730
+ { name: 'gemini-2.0-flash', displayName: 'Gemini 2.0 Flash', description: 'Balanced speed and capability' },
731
+ { name: 'gemini-2.0-flash-lite', displayName: 'Gemini 2.0 Flash Lite', description: 'Fastest and most cost-effective' },
732
+ { name: 'gemini-1.5-pro', displayName: 'Gemini 1.5 Pro', description: 'Previous generation pro model' },
733
+ { name: 'gemini-1.5-flash', displayName: 'Gemini 1.5 Flash', description: 'Previous generation flash model' },
734
+ ];
735
+
736
+ if (!key) {
737
+ // Return default models if no API key
738
+ return defaultModels;
739
+ }
740
+
741
+ try {
742
+ const provider = new GeminiProvider({
743
+ type: 'gemini',
744
+ model: '',
745
+ geminiApiKey: key,
746
+ });
747
+ return await provider.getAvailableModels();
748
+ } catch (error: any) {
749
+ console.error('Failed to fetch Gemini models:', error);
750
+ // Return default models on error instead of empty array
751
+ return defaultModels;
752
+ }
753
+ }
754
+
755
+ /**
756
+ * Fetch available OpenRouter models from the API
757
+ */
758
+ static async getOpenRouterModels(apiKey?: string): Promise<Array<{ id: string; name: string; context_length: number }>> {
759
+ const settings = this.loadSettings();
760
+ // Normalize empty strings to undefined
761
+ const normalizedApiKey = apiKey?.trim() || undefined;
762
+ const key = normalizedApiKey || settings.openrouter?.apiKey;
763
+
764
+ const defaultModels = [
765
+ { id: 'anthropic/claude-3.5-sonnet', name: 'Claude 3.5 Sonnet', context_length: 200000 },
766
+ { id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus', context_length: 200000 },
767
+ { id: 'openai/gpt-4o', name: 'GPT-4o', context_length: 128000 },
768
+ { id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', context_length: 128000 },
769
+ { id: 'google/gemini-pro-1.5', name: 'Gemini Pro 1.5', context_length: 1000000 },
770
+ { id: 'meta-llama/llama-3.1-405b-instruct', name: 'Llama 3.1 405B', context_length: 131072 },
771
+ ];
772
+
773
+ if (!key) {
774
+ // Return default models if no API key
775
+ return defaultModels;
776
+ }
777
+
778
+ try {
779
+ const provider = new OpenRouterProvider({
780
+ type: 'openrouter',
781
+ model: '',
782
+ openrouterApiKey: key,
783
+ });
784
+ return await provider.getAvailableModels();
785
+ } catch (error: any) {
786
+ console.error('Failed to fetch OpenRouter models:', error);
787
+ // Return default models on error instead of empty array
788
+ return defaultModels;
789
+ }
790
+ }
791
+
792
+ /**
793
+ * Fetch available OpenAI models
794
+ * For API key auth: uses the models.list API via OpenAI SDK
795
+ * For OAuth auth: uses pi-ai SDK's model list for openai-codex provider
796
+ */
797
+ static async getOpenAIModels(apiKey?: string): Promise<Array<{ id: string; name: string; description: string }>> {
798
+ const settings = this.loadSettings();
799
+ // Normalize empty strings to undefined
800
+ const normalizedApiKey = apiKey?.trim() || undefined;
801
+ const key = normalizedApiKey || settings.openai?.apiKey;
802
+ // Check for OAuth access token if no API key
803
+ const accessToken = settings.openai?.accessToken;
804
+ const refreshToken = settings.openai?.refreshToken;
805
+
806
+ const defaultModels = [
807
+ { id: 'gpt-4o', name: 'GPT-4o', description: 'Most capable model for complex tasks' },
808
+ { id: 'gpt-4o-mini', name: 'GPT-4o Mini', description: 'Fast and affordable for most tasks' },
809
+ { id: 'gpt-4-turbo', name: 'GPT-4 Turbo', description: 'Previous generation flagship' },
810
+ { id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', description: 'Fast and cost-effective' },
811
+ { id: 'o1', name: 'o1', description: 'Advanced reasoning model' },
812
+ { id: 'o1-mini', name: 'o1 Mini', description: 'Fast reasoning model' },
813
+ ];
814
+
815
+ // For OAuth users, use pi-ai SDK's model list directly
816
+ if (accessToken && refreshToken && !key) {
817
+ console.log('[OpenAI] Using OAuth - fetching models from pi-ai SDK...');
818
+ try {
819
+ const piAiModels = getPiAiModels('openai-codex');
820
+ const models = piAiModels.map((m) => ({
821
+ id: m.id,
822
+ name: m.name || this.formatOpenAIModelName(m.id),
823
+ description: this.getOpenAIModelDescription(m.id),
824
+ }));
825
+
826
+ // Sort by priority (ChatGPT internal models)
827
+ models.sort((a, b) => {
828
+ const priority = (id: string) => {
829
+ if (id.includes('5.1-codex-mini')) return 0;
830
+ if (id.includes('5.1-codex-max')) return 1;
831
+ if (id === 'gpt-5.1') return 2;
832
+ if (id.includes('5.2-codex')) return 3;
833
+ if (id === 'gpt-5.2') return 4;
834
+ return 5;
835
+ };
836
+ return priority(a.id) - priority(b.id);
837
+ });
838
+
839
+ console.log(`[OpenAI] Found ${models.length} models via pi-ai SDK`);
840
+ return models;
841
+ } catch (error) {
842
+ console.error('[OpenAI] Failed to get models from pi-ai SDK:', error);
843
+ // Return ChatGPT-specific defaults for OAuth users
844
+ return [
845
+ { id: 'gpt-5.1-codex-mini', name: 'GPT-5.1 Codex Mini', description: 'Fast and efficient for most tasks' },
846
+ { id: 'gpt-5.1-codex-max', name: 'GPT-5.1 Codex Max', description: 'Maximum capability for complex tasks' },
847
+ { id: 'gpt-5.1', name: 'GPT-5.1', description: 'Balanced performance and capability' },
848
+ { id: 'gpt-5.2-codex', name: 'GPT-5.2 Codex', description: 'Advanced reasoning model' },
849
+ { id: 'gpt-5.2', name: 'GPT-5.2', description: 'Most advanced reasoning' },
850
+ ];
851
+ }
852
+ }
853
+
854
+ if (!key) {
855
+ // Return default models if no authentication
856
+ return defaultModels;
857
+ }
858
+
859
+ try {
860
+ // For API key, use the OpenAI provider
861
+ const provider = new OpenAIProvider({
862
+ type: 'openai',
863
+ model: '',
864
+ openaiApiKey: key,
865
+ });
866
+ return await provider.getAvailableModels();
867
+ } catch (error: any) {
868
+ console.error('Failed to fetch OpenAI models:', error);
869
+ // Return default models on error instead of empty array
870
+ return defaultModels;
871
+ }
872
+ }
873
+
874
+ /**
875
+ * Format OpenAI model ID to display name
876
+ */
877
+ private static formatOpenAIModelName(modelId: string): string {
878
+ // Public API models
879
+ if (modelId === 'gpt-4o') return 'GPT-4o';
880
+ if (modelId === 'gpt-4o-mini') return 'GPT-4o Mini';
881
+ if (modelId.includes('gpt-4o-')) return `GPT-4o (${modelId.replace('gpt-4o-', '')})`;
882
+ if (modelId === 'gpt-4-turbo') return 'GPT-4 Turbo';
883
+ if (modelId === 'gpt-4') return 'GPT-4';
884
+ if (modelId === 'gpt-3.5-turbo') return 'GPT-3.5 Turbo';
885
+ if (modelId === 'o1') return 'o1';
886
+ if (modelId === 'o1-mini') return 'o1 Mini';
887
+ if (modelId === 'o1-preview') return 'o1 Preview';
888
+ if (modelId === 'o3-mini') return 'o3 Mini';
889
+ // ChatGPT internal models
890
+ if (modelId === 'gpt-5.1') return 'GPT-5.1';
891
+ if (modelId === 'gpt-5.1-codex-mini') return 'GPT-5.1 Codex Mini';
892
+ if (modelId === 'gpt-5.1-codex-max') return 'GPT-5.1 Codex Max';
893
+ if (modelId === 'gpt-5.2') return 'GPT-5.2';
894
+ if (modelId === 'gpt-5.2-codex') return 'GPT-5.2 Codex';
895
+ return modelId;
896
+ }
897
+
898
+ /**
899
+ * Get OpenAI model description
900
+ */
901
+ private static getOpenAIModelDescription(modelId: string): string {
902
+ // Public API models
903
+ if (modelId.includes('gpt-4o') && !modelId.includes('mini')) return 'Most capable model for complex tasks';
904
+ if (modelId.includes('gpt-4o-mini')) return 'Fast and affordable for most tasks';
905
+ if (modelId.includes('gpt-4-turbo')) return 'Previous generation flagship';
906
+ if (modelId.includes('gpt-4')) return 'High capability model';
907
+ if (modelId.includes('gpt-3.5')) return 'Fast and cost-effective';
908
+ if (modelId === 'o1' || modelId === 'o1-preview') return 'Advanced reasoning model';
909
+ if (modelId === 'o1-mini') return 'Fast reasoning model';
910
+ if (modelId.includes('o3')) return 'Next generation reasoning';
911
+ // ChatGPT internal models
912
+ if (modelId === 'gpt-5.1') return 'Balanced performance and capability';
913
+ if (modelId === 'gpt-5.1-codex-mini') return 'Fast and efficient for most tasks';
914
+ if (modelId === 'gpt-5.1-codex-max') return 'Maximum capability for complex tasks';
915
+ if (modelId === 'gpt-5.2') return 'Most advanced reasoning';
916
+ if (modelId === 'gpt-5.2-codex') return 'Advanced reasoning model';
917
+ return 'OpenAI model';
918
+ }
919
+
920
+ /**
921
+ * Save cached models for a provider
922
+ */
923
+ static saveCachedModels(
924
+ providerType: 'gemini' | 'openrouter' | 'ollama' | 'bedrock' | 'openai',
925
+ models: CachedModelInfo[]
926
+ ): void {
927
+ const settings = this.loadSettings();
928
+
929
+ switch (providerType) {
930
+ case 'gemini':
931
+ settings.cachedGeminiModels = models;
932
+ break;
933
+ case 'openrouter':
934
+ settings.cachedOpenRouterModels = models;
935
+ break;
936
+ case 'ollama':
937
+ settings.cachedOllamaModels = models;
938
+ break;
939
+ case 'bedrock':
940
+ settings.cachedBedrockModels = models;
941
+ break;
942
+ case 'openai':
943
+ settings.cachedOpenAIModels = models;
944
+ break;
945
+ }
946
+
947
+ this.saveSettings(settings);
948
+ }
949
+
950
+ /**
951
+ * Get cached models for a provider
952
+ */
953
+ static getCachedModels(providerType: 'gemini' | 'openrouter' | 'ollama' | 'bedrock' | 'openai'): CachedModelInfo[] | undefined {
954
+ const settings = this.loadSettings();
955
+
956
+ switch (providerType) {
957
+ case 'gemini':
958
+ return settings.cachedGeminiModels;
959
+ case 'openrouter':
960
+ return settings.cachedOpenRouterModels;
961
+ case 'ollama':
962
+ return settings.cachedOllamaModels;
963
+ case 'bedrock':
964
+ return settings.cachedBedrockModels;
965
+ case 'openai':
966
+ return settings.cachedOpenAIModels;
967
+ default:
968
+ return undefined;
969
+ }
970
+ }
971
+ }