@build-astron-co/nimbus 0.2.0

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 (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,470 @@
1
+ /**
2
+ * Provider Registry & Validation
3
+ * Static metadata and validation functions for LLM providers
4
+ */
5
+
6
+ import type { LLMProviderName, ProviderInfo, ProviderValidationResult } from './types';
7
+
8
+ /**
9
+ * Registry of all supported LLM providers
10
+ */
11
+ export const PROVIDER_REGISTRY: Record<LLMProviderName, ProviderInfo> = {
12
+ anthropic: {
13
+ name: 'anthropic',
14
+ displayName: 'Anthropic (Claude)',
15
+ description: 'Claude Sonnet 4, Opus 4, Haiku 4',
16
+ envVarName: 'ANTHROPIC_API_KEY',
17
+ apiKeyUrl: 'https://console.anthropic.com/settings/keys',
18
+ requiresApiKey: true,
19
+ models: [
20
+ { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', isDefault: true },
21
+ { id: 'claude-opus-4-20250514', name: 'Claude Opus 4' },
22
+ { id: 'claude-haiku-4-20250514', name: 'Claude Haiku 4' },
23
+ ],
24
+ },
25
+ openai: {
26
+ name: 'openai',
27
+ displayName: 'OpenAI (GPT)',
28
+ description: 'GPT-4o, GPT-4o-mini',
29
+ envVarName: 'OPENAI_API_KEY',
30
+ apiKeyUrl: 'https://platform.openai.com/api-keys',
31
+ requiresApiKey: true,
32
+ models: [
33
+ { id: 'gpt-4o', name: 'GPT-4o', isDefault: true },
34
+ { id: 'gpt-4o-mini', name: 'GPT-4o Mini' },
35
+ { id: 'gpt-4-turbo', name: 'GPT-4 Turbo' },
36
+ ],
37
+ },
38
+ google: {
39
+ name: 'google',
40
+ displayName: 'Google (Gemini)',
41
+ description: 'Gemini 2.0 Flash, Gemini 1.5 Pro',
42
+ envVarName: 'GOOGLE_API_KEY',
43
+ apiKeyUrl: 'https://aistudio.google.com/app/apikey',
44
+ requiresApiKey: true,
45
+ models: [
46
+ { id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash', isDefault: true },
47
+ { id: 'gemini-1.5-pro', name: 'Gemini 1.5 Pro' },
48
+ { id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash' },
49
+ ],
50
+ },
51
+ openrouter: {
52
+ name: 'openrouter',
53
+ displayName: 'OpenRouter',
54
+ description: 'Access multiple models via OpenRouter',
55
+ envVarName: 'OPENROUTER_API_KEY',
56
+ apiKeyUrl: 'https://openrouter.ai/keys',
57
+ requiresApiKey: true,
58
+ models: [
59
+ {
60
+ id: 'anthropic/claude-sonnet-4',
61
+ name: 'Claude Sonnet 4 (via OpenRouter)',
62
+ isDefault: true,
63
+ },
64
+ { id: 'openai/gpt-4o', name: 'GPT-4o (via OpenRouter)' },
65
+ { id: 'google/gemini-pro', name: 'Gemini Pro (via OpenRouter)' },
66
+ { id: 'meta-llama/llama-3.1-405b-instruct', name: 'Llama 3.1 405B' },
67
+ ],
68
+ },
69
+ ollama: {
70
+ name: 'ollama',
71
+ displayName: 'Ollama (Local)',
72
+ description: 'Local models: Llama 3.2, CodeLlama, Mistral',
73
+ requiresApiKey: false,
74
+ supportsBaseUrl: true,
75
+ defaultBaseUrl: 'http://localhost:11434',
76
+ models: [
77
+ { id: 'llama3.2', name: 'Llama 3.2', isDefault: true },
78
+ { id: 'codellama', name: 'CodeLlama' },
79
+ { id: 'mistral', name: 'Mistral' },
80
+ { id: 'deepseek-coder', name: 'DeepSeek Coder' },
81
+ ],
82
+ },
83
+ groq: {
84
+ name: 'groq',
85
+ displayName: 'Groq',
86
+ description: 'Ultra-fast inference: Llama 3, Mixtral',
87
+ envVarName: 'GROQ_API_KEY',
88
+ apiKeyUrl: 'https://console.groq.com/keys',
89
+ requiresApiKey: true,
90
+ models: [
91
+ { id: 'llama-3.3-70b-versatile', name: 'Llama 3.3 70B', isDefault: true },
92
+ { id: 'llama-3.1-8b-instant', name: 'Llama 3.1 8B Instant' },
93
+ { id: 'mixtral-8x7b-32768', name: 'Mixtral 8x7B' },
94
+ ],
95
+ },
96
+ together: {
97
+ name: 'together',
98
+ displayName: 'Together AI',
99
+ description: 'Open-source models: Llama, CodeLlama, Mistral',
100
+ envVarName: 'TOGETHER_API_KEY',
101
+ apiKeyUrl: 'https://api.together.xyz/settings/api-keys',
102
+ requiresApiKey: true,
103
+ models: [
104
+ {
105
+ id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
106
+ name: 'Llama 3.3 70B Turbo',
107
+ isDefault: true,
108
+ },
109
+ { id: 'meta-llama/Llama-3.1-8B-Instruct-Turbo', name: 'Llama 3.1 8B Turbo' },
110
+ { id: 'mistralai/Mixtral-8x7B-Instruct-v0.1', name: 'Mixtral 8x7B' },
111
+ ],
112
+ },
113
+ deepseek: {
114
+ name: 'deepseek',
115
+ displayName: 'DeepSeek',
116
+ description: 'DeepSeek V3, DeepSeek Coder',
117
+ envVarName: 'DEEPSEEK_API_KEY',
118
+ apiKeyUrl: 'https://platform.deepseek.com/api_keys',
119
+ requiresApiKey: true,
120
+ models: [
121
+ { id: 'deepseek-chat', name: 'DeepSeek V3', isDefault: true },
122
+ { id: 'deepseek-coder', name: 'DeepSeek Coder' },
123
+ ],
124
+ },
125
+ fireworks: {
126
+ name: 'fireworks',
127
+ displayName: 'Fireworks AI',
128
+ description: 'Fast inference: Llama, Mixtral, FireFunction',
129
+ envVarName: 'FIREWORKS_API_KEY',
130
+ apiKeyUrl: 'https://fireworks.ai/api-keys',
131
+ requiresApiKey: true,
132
+ models: [
133
+ {
134
+ id: 'accounts/fireworks/models/llama-v3p3-70b-instruct',
135
+ name: 'Llama 3.3 70B',
136
+ isDefault: true,
137
+ },
138
+ { id: 'accounts/fireworks/models/mixtral-8x7b-instruct', name: 'Mixtral 8x7B' },
139
+ ],
140
+ },
141
+ perplexity: {
142
+ name: 'perplexity',
143
+ displayName: 'Perplexity',
144
+ description: 'Search-augmented AI: Sonar models',
145
+ envVarName: 'PERPLEXITY_API_KEY',
146
+ apiKeyUrl: 'https://www.perplexity.ai/settings/api',
147
+ requiresApiKey: true,
148
+ models: [
149
+ { id: 'sonar-pro', name: 'Sonar Pro', isDefault: true },
150
+ { id: 'sonar', name: 'Sonar' },
151
+ ],
152
+ },
153
+ bedrock: {
154
+ name: 'bedrock',
155
+ displayName: 'AWS Bedrock',
156
+ description: 'Claude via AWS Bedrock (uses IAM credentials)',
157
+ envVarName: 'AWS_ACCESS_KEY_ID',
158
+ requiresApiKey: false,
159
+ supportsBaseUrl: true,
160
+ defaultBaseUrl: 'us-east-1',
161
+ models: [
162
+ {
163
+ id: 'anthropic.claude-sonnet-4-20250514-v1:0',
164
+ name: 'Claude Sonnet 4 (Bedrock)',
165
+ isDefault: true,
166
+ },
167
+ { id: 'anthropic.claude-haiku-4-20250514-v1:0', name: 'Claude Haiku 4 (Bedrock)' },
168
+ ],
169
+ },
170
+ };
171
+
172
+ /**
173
+ * Get provider info from registry
174
+ */
175
+ export function getProviderInfo(name: LLMProviderName): ProviderInfo {
176
+ return PROVIDER_REGISTRY[name];
177
+ }
178
+
179
+ /**
180
+ * Get all provider names
181
+ */
182
+ export function getProviderNames(): LLMProviderName[] {
183
+ return Object.keys(PROVIDER_REGISTRY) as LLMProviderName[];
184
+ }
185
+
186
+ /**
187
+ * Get the default model for a provider
188
+ */
189
+ export function getDefaultModel(name: LLMProviderName): string {
190
+ const info = PROVIDER_REGISTRY[name];
191
+ const defaultModel = info.models.find(m => m.isDefault);
192
+ return defaultModel?.id || info.models[0].id;
193
+ }
194
+
195
+ /**
196
+ * Validate a provider's API key or configuration
197
+ * Makes lightweight test API calls to verify credentials
198
+ */
199
+ export async function validateProviderApiKey(
200
+ provider: LLMProviderName,
201
+ apiKey?: string,
202
+ baseUrl?: string
203
+ ): Promise<ProviderValidationResult> {
204
+ try {
205
+ switch (provider) {
206
+ case 'anthropic':
207
+ return await validateAnthropic(apiKey);
208
+ case 'openai':
209
+ return await validateOpenAI(apiKey);
210
+ case 'google':
211
+ return await validateGoogle(apiKey);
212
+ case 'openrouter':
213
+ return await validateOpenRouter(apiKey);
214
+ case 'ollama':
215
+ return await validateOllama(baseUrl);
216
+ case 'groq':
217
+ return await validateOpenAICompatible(apiKey, 'https://api.groq.com/openai/v1/models');
218
+ case 'together':
219
+ return await validateOpenAICompatible(apiKey, 'https://api.together.xyz/v1/models');
220
+ case 'deepseek':
221
+ return await validateOpenAICompatible(apiKey, 'https://api.deepseek.com/v1/models');
222
+ case 'fireworks':
223
+ return await validateOpenAICompatible(
224
+ apiKey,
225
+ 'https://api.fireworks.ai/inference/v1/models'
226
+ );
227
+ case 'perplexity':
228
+ return await validateOpenAICompatible(apiKey, 'https://api.perplexity.ai/models');
229
+ case 'bedrock':
230
+ return await validateBedrock(baseUrl);
231
+ default:
232
+ return { valid: false, error: `Unknown provider: ${provider}` };
233
+ }
234
+ } catch (error) {
235
+ const message = error instanceof Error ? error.message : 'Unknown error';
236
+ return { valid: false, error: message };
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Validate Anthropic API key
242
+ * Uses POST /v1/messages with max_tokens: 1
243
+ */
244
+ async function validateAnthropic(apiKey?: string): Promise<ProviderValidationResult> {
245
+ if (!apiKey) {
246
+ return { valid: false, error: 'API key is required' };
247
+ }
248
+
249
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
250
+ method: 'POST',
251
+ headers: {
252
+ 'Content-Type': 'application/json',
253
+ 'x-api-key': apiKey,
254
+ 'anthropic-version': '2023-06-01',
255
+ },
256
+ body: JSON.stringify({
257
+ model: 'claude-haiku-4-20250514',
258
+ max_tokens: 1,
259
+ messages: [{ role: 'user', content: 'Hi' }],
260
+ }),
261
+ });
262
+
263
+ if (response.ok) {
264
+ return { valid: true };
265
+ }
266
+
267
+ // Check for specific error types
268
+ if (response.status === 401) {
269
+ return { valid: false, error: 'Invalid API key' };
270
+ }
271
+
272
+ if (response.status === 400) {
273
+ // 400 errors with valid auth often mean the key is valid but request is malformed
274
+ // For validation purposes, a 400 with auth working is acceptable
275
+ const body = (await response.json().catch(() => ({}))) as { error?: { type?: string } };
276
+ if (body.error?.type === 'invalid_request_error') {
277
+ // Key is valid, just request was bad
278
+ return { valid: true };
279
+ }
280
+ }
281
+
282
+ const errorText = await response.text().catch(() => 'Unknown error');
283
+ return { valid: false, error: `API error: ${response.status} - ${errorText}` };
284
+ }
285
+
286
+ /**
287
+ * Validate OpenAI API key
288
+ * Uses GET /v1/models
289
+ */
290
+ async function validateOpenAI(apiKey?: string): Promise<ProviderValidationResult> {
291
+ if (!apiKey) {
292
+ return { valid: false, error: 'API key is required' };
293
+ }
294
+
295
+ const response = await fetch('https://api.openai.com/v1/models', {
296
+ method: 'GET',
297
+ headers: {
298
+ Authorization: `Bearer ${apiKey}`,
299
+ },
300
+ });
301
+
302
+ if (response.ok) {
303
+ const data = (await response.json()) as { data?: Array<{ id: string }> };
304
+ const models = data.data?.map(m => m.id) || [];
305
+ return { valid: true, models };
306
+ }
307
+
308
+ if (response.status === 401) {
309
+ return { valid: false, error: 'Invalid API key' };
310
+ }
311
+
312
+ const errorText = await response.text().catch(() => 'Unknown error');
313
+ return { valid: false, error: `API error: ${response.status} - ${errorText}` };
314
+ }
315
+
316
+ /**
317
+ * Validate Google API key
318
+ * Uses GET /v1/models?key=<key>
319
+ */
320
+ async function validateGoogle(apiKey?: string): Promise<ProviderValidationResult> {
321
+ if (!apiKey) {
322
+ return { valid: false, error: 'API key is required' };
323
+ }
324
+
325
+ const response = await fetch(
326
+ `https://generativelanguage.googleapis.com/v1/models?key=${encodeURIComponent(apiKey)}`,
327
+ {
328
+ method: 'GET',
329
+ }
330
+ );
331
+
332
+ if (response.ok) {
333
+ const data = (await response.json()) as { models?: Array<{ name: string }> };
334
+ const models = data.models?.map(m => m.name) || [];
335
+ return { valid: true, models };
336
+ }
337
+
338
+ if (response.status === 400 || response.status === 403) {
339
+ return { valid: false, error: 'Invalid API key' };
340
+ }
341
+
342
+ const errorText = await response.text().catch(() => 'Unknown error');
343
+ return { valid: false, error: `API error: ${response.status} - ${errorText}` };
344
+ }
345
+
346
+ /**
347
+ * Validate OpenRouter API key
348
+ * Uses GET /api/v1/models with Bearer auth
349
+ */
350
+ async function validateOpenRouter(apiKey?: string): Promise<ProviderValidationResult> {
351
+ if (!apiKey) {
352
+ return { valid: false, error: 'API key is required' };
353
+ }
354
+
355
+ const response = await fetch('https://openrouter.ai/api/v1/models', {
356
+ method: 'GET',
357
+ headers: {
358
+ Authorization: `Bearer ${apiKey}`,
359
+ },
360
+ });
361
+
362
+ if (response.ok) {
363
+ const data = (await response.json()) as { data?: Array<{ id: string }> };
364
+ const models = data.data?.map(m => m.id) || [];
365
+ return { valid: true, models };
366
+ }
367
+
368
+ if (response.status === 401) {
369
+ return { valid: false, error: 'Invalid API key' };
370
+ }
371
+
372
+ const errorText = await response.text().catch(() => 'Unknown error');
373
+ return { valid: false, error: `API error: ${response.status} - ${errorText}` };
374
+ }
375
+
376
+ /**
377
+ * Validate OpenAI-compatible provider API key
378
+ * Uses GET /v1/models (or equivalent) with Bearer auth
379
+ */
380
+ async function validateOpenAICompatible(
381
+ apiKey?: string,
382
+ modelsUrl?: string
383
+ ): Promise<ProviderValidationResult> {
384
+ if (!apiKey) {
385
+ return { valid: false, error: 'API key is required' };
386
+ }
387
+
388
+ const url = modelsUrl || 'https://api.openai.com/v1/models';
389
+
390
+ const response = await fetch(url, {
391
+ method: 'GET',
392
+ headers: {
393
+ Authorization: `Bearer ${apiKey}`,
394
+ },
395
+ });
396
+
397
+ if (response.ok) {
398
+ return { valid: true };
399
+ }
400
+
401
+ if (response.status === 401 || response.status === 403) {
402
+ return { valid: false, error: 'Invalid API key' };
403
+ }
404
+
405
+ const errorText = await response.text().catch(() => 'Unknown error');
406
+ return { valid: false, error: `API error: ${response.status} - ${errorText}` };
407
+ }
408
+
409
+ /**
410
+ * Validate Ollama connection
411
+ * Uses GET <baseUrl>/api/tags (no key needed)
412
+ */
413
+ async function validateOllama(baseUrl?: string): Promise<ProviderValidationResult> {
414
+ const url = baseUrl || 'http://localhost:11434';
415
+
416
+ try {
417
+ const response = await fetch(`${url}/api/tags`, {
418
+ method: 'GET',
419
+ });
420
+
421
+ if (response.ok) {
422
+ const data = (await response.json()) as { models?: Array<{ name: string }> };
423
+ const models = data.models?.map(m => m.name) || [];
424
+ return { valid: true, models };
425
+ }
426
+
427
+ const errorText = await response.text().catch(() => 'Unknown error');
428
+ return { valid: false, error: `Ollama API error: ${response.status} - ${errorText}` };
429
+ } catch (error) {
430
+ if (error instanceof TypeError && error.message.includes('fetch')) {
431
+ return {
432
+ valid: false,
433
+ error: `Cannot connect to Ollama at ${url}. Is Ollama running?`,
434
+ };
435
+ }
436
+ throw error;
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Validate AWS Bedrock credentials
442
+ * Checks for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY env vars,
443
+ * or AWS credentials file (~/.aws/credentials).
444
+ */
445
+ async function validateBedrock(_region?: string): Promise<ProviderValidationResult> {
446
+ const hasAccessKey = !!process.env.AWS_ACCESS_KEY_ID;
447
+ const hasSecretKey = !!process.env.AWS_SECRET_ACCESS_KEY;
448
+ const hasProfile = !!process.env.AWS_PROFILE;
449
+
450
+ // Check for AWS credentials file
451
+ const fs = await import('fs');
452
+ const path = await import('path');
453
+ const os = await import('os');
454
+ const credentialsPath = path.join(os.homedir(), '.aws', 'credentials');
455
+ const hasCredentialsFile = fs.existsSync(credentialsPath);
456
+
457
+ if (hasAccessKey && hasSecretKey) {
458
+ return { valid: true };
459
+ }
460
+
461
+ if (hasProfile || hasCredentialsFile) {
462
+ return { valid: true };
463
+ }
464
+
465
+ return {
466
+ valid: false,
467
+ error:
468
+ 'AWS credentials not found. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, or configure ~/.aws/credentials.',
469
+ };
470
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * SSO Device Flow
3
+ * Enterprise SSO authentication using device code flow
4
+ */
5
+
6
+ import { authClient } from '../clients/enterprise-client';
7
+ import type { DeviceCodeResponse } from '../types';
8
+
9
+ /**
10
+ * SSO Device Flow for enterprise authentication
11
+ * Uses the auth-service for device code management
12
+ */
13
+ export class SSODeviceFlow {
14
+ private deviceCode: string | null = null;
15
+ private interval: number = 5;
16
+ private expiresAt: number = 0;
17
+
18
+ /**
19
+ * Initiate device code flow
20
+ * Returns device code and user code for the client
21
+ */
22
+ async initiate(): Promise<DeviceCodeResponse> {
23
+ const response = await authClient.initiateDeviceFlow();
24
+
25
+ this.deviceCode = response.deviceCode;
26
+ this.interval = response.interval || 5;
27
+ this.expiresAt = Date.now() + response.expiresIn * 1000;
28
+
29
+ return response;
30
+ }
31
+
32
+ /**
33
+ * Poll for authorization
34
+ * Returns access token when user authorizes, null if pending
35
+ */
36
+ async poll(): Promise<string | null> {
37
+ if (!this.deviceCode) {
38
+ throw new Error('Device code not initiated. Call initiate() first.');
39
+ }
40
+
41
+ if (Date.now() > this.expiresAt) {
42
+ throw new Error('Device code expired. Please start the login process again.');
43
+ }
44
+
45
+ const response = await authClient.pollDeviceCode(this.deviceCode);
46
+
47
+ if (response.accessToken) {
48
+ return response.accessToken;
49
+ }
50
+
51
+ if (response.error === 'authorization_pending') {
52
+ return null;
53
+ }
54
+
55
+ if (response.error === 'slow_down') {
56
+ this.interval += 5;
57
+ return null;
58
+ }
59
+
60
+ if (response.error === 'expired_token') {
61
+ throw new Error('Device code expired. Please start the login process again.');
62
+ }
63
+
64
+ if (response.error === 'access_denied') {
65
+ throw new Error('Authorization was denied.');
66
+ }
67
+
68
+ throw new Error(response.errorDescription || response.error || 'Unknown error');
69
+ }
70
+
71
+ /**
72
+ * Get polling interval in milliseconds
73
+ */
74
+ getPollingInterval(): number {
75
+ return this.interval * 1000;
76
+ }
77
+
78
+ /**
79
+ * Wait for authorization by polling
80
+ * Returns access token when user completes authorization
81
+ */
82
+ async waitForAuthorization(onPoll?: () => void, abortSignal?: AbortSignal): Promise<string> {
83
+ for (;;) {
84
+ if (abortSignal?.aborted) {
85
+ throw new Error('Authorization cancelled');
86
+ }
87
+
88
+ const token = await this.poll();
89
+
90
+ if (token) {
91
+ return token;
92
+ }
93
+
94
+ onPoll?.();
95
+ await this.sleep(this.getPollingInterval());
96
+ }
97
+ }
98
+
99
+ private sleep(ms: number): Promise<void> {
100
+ return new Promise(resolve => setTimeout(resolve, ms));
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Validate an SSO access token
106
+ */
107
+ export async function validateSSOToken(accessToken: string): Promise<{
108
+ valid: boolean;
109
+ userId?: string;
110
+ teamId?: string;
111
+ }> {
112
+ return authClient.validateToken(accessToken);
113
+ }