@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,433 @@
1
+ /**
2
+ * Anthropic Claude Provider
3
+ * Supports Claude Sonnet 4, Haiku 4, and Opus 4
4
+ */
5
+
6
+ import Anthropic from '@anthropic-ai/sdk';
7
+ import {
8
+ BaseProvider,
9
+ type CompletionRequest,
10
+ type LLMMessage,
11
+ type LLMResponse,
12
+ type StreamChunk,
13
+ type ToolCall,
14
+ type ToolCompletionRequest,
15
+ type ToolDefinition,
16
+ } from '../types';
17
+ import { getProviderApiKey } from '../auth-bridge';
18
+
19
+ export class AnthropicProvider extends BaseProvider {
20
+ name = 'anthropic';
21
+ private client: Anthropic;
22
+ private defaultModel = 'claude-sonnet-4-20250514';
23
+
24
+ constructor(apiKey?: string) {
25
+ super();
26
+ this.client = new Anthropic({
27
+ apiKey: apiKey || getProviderApiKey('anthropic') || process.env.ANTHROPIC_API_KEY,
28
+ });
29
+ }
30
+
31
+ async complete(request: CompletionRequest): Promise<LLMResponse> {
32
+ const systemPrompt = this.extractSystemPrompt(request.messages);
33
+ const messages = this.convertMessages(this.filterSystemMessages(request.messages));
34
+
35
+ const response = await this.client.messages.create({
36
+ model: request.model || this.defaultModel,
37
+ max_tokens: request.maxTokens || 4096,
38
+ messages,
39
+ system: systemPrompt,
40
+ temperature: request.temperature,
41
+ stop_sequences: request.stopSequences,
42
+ });
43
+
44
+ return this.convertResponse(response);
45
+ }
46
+
47
+ async *stream(request: CompletionRequest): AsyncIterable<StreamChunk> {
48
+ const systemPrompt = this.extractSystemPrompt(request.messages);
49
+ const messages = this.convertMessages(this.filterSystemMessages(request.messages));
50
+
51
+ const stream = await this.client.messages.stream({
52
+ model: request.model || this.defaultModel,
53
+ max_tokens: request.maxTokens || 4096,
54
+ messages,
55
+ system: systemPrompt,
56
+ temperature: request.temperature,
57
+ stop_sequences: request.stopSequences,
58
+ });
59
+
60
+ let usage: { promptTokens: number; completionTokens: number; totalTokens: number } | undefined;
61
+ let inputTokensFromStart = 0;
62
+
63
+ for await (const event of stream) {
64
+ if (event.type === 'message_start') {
65
+ const msgEvent = event as any;
66
+ if (msgEvent.message?.usage?.input_tokens) {
67
+ inputTokensFromStart = msgEvent.message.usage.input_tokens;
68
+ }
69
+ } else if (event.type === 'content_block_delta') {
70
+ if (event.delta.type === 'text_delta') {
71
+ yield {
72
+ content: event.delta.text,
73
+ done: false,
74
+ };
75
+ }
76
+ } else if (event.type === 'message_delta') {
77
+ // Capture usage: input_tokens from message_start, output_tokens from message_delta
78
+ const deltaEvent = event as any;
79
+ if (deltaEvent.usage) {
80
+ const inputTokens = inputTokensFromStart || deltaEvent.usage.input_tokens || 0;
81
+ const outputTokens = deltaEvent.usage.output_tokens || 0;
82
+ usage = {
83
+ promptTokens: inputTokens,
84
+ completionTokens: outputTokens,
85
+ totalTokens: inputTokens + outputTokens,
86
+ };
87
+ }
88
+ } else if (event.type === 'message_stop') {
89
+ // If we didn't get usage from message_delta, try to get final message usage
90
+ if (!usage) {
91
+ try {
92
+ const finalMessage = await stream.finalMessage();
93
+ usage = {
94
+ promptTokens: finalMessage.usage.input_tokens,
95
+ completionTokens: finalMessage.usage.output_tokens,
96
+ totalTokens: finalMessage.usage.input_tokens + finalMessage.usage.output_tokens,
97
+ };
98
+ } catch {
99
+ // Non-critical: token count will be estimated
100
+ }
101
+ }
102
+ yield { done: true, usage };
103
+ }
104
+ }
105
+ }
106
+
107
+ async completeWithTools(request: ToolCompletionRequest): Promise<LLMResponse> {
108
+ const systemPrompt = this.extractSystemPrompt(request.messages);
109
+ const messages = this.convertMessages(this.filterSystemMessages(request.messages));
110
+
111
+ const toolChoice = this.convertToolChoice(request.toolChoice);
112
+ const response = await this.client.messages.create({
113
+ model: request.model || this.defaultModel,
114
+ max_tokens: request.maxTokens || 4096,
115
+ messages,
116
+ system: systemPrompt,
117
+ ...(request.toolChoice !== 'none' && { tools: this.convertTools(request.tools) }),
118
+ ...(toolChoice && { tool_choice: toolChoice }),
119
+ temperature: request.temperature,
120
+ });
121
+
122
+ return this.convertResponse(response);
123
+ }
124
+
125
+ async *streamWithTools(request: ToolCompletionRequest): AsyncIterable<StreamChunk> {
126
+ const systemPrompt = this.extractSystemPrompt(request.messages);
127
+ const messages = this.convertMessages(this.filterSystemMessages(request.messages));
128
+
129
+ const toolChoice = this.convertToolChoice(request.toolChoice);
130
+ const stream = await this.client.messages.stream({
131
+ model: request.model || this.defaultModel,
132
+ max_tokens: request.maxTokens || 4096,
133
+ messages,
134
+ system: systemPrompt,
135
+ ...(request.toolChoice !== 'none' && { tools: this.convertTools(request.tools) }),
136
+ ...(toolChoice && { tool_choice: toolChoice }),
137
+ temperature: request.temperature,
138
+ });
139
+
140
+ let usage: StreamChunk['usage'] | undefined;
141
+ let inputTokensFromStart = 0; // Captured from message_start event
142
+ const toolCallAccumulator = new Map<number, { id: string; name: string; arguments: string }>();
143
+ let toolCallIndex = 0;
144
+ let currentToolId = '';
145
+ let currentToolName = '';
146
+
147
+ for await (const event of stream) {
148
+ // Capture input tokens from message_start (sent at beginning of stream)
149
+ if (event.type === 'message_start') {
150
+ const msgEvent = event as any;
151
+ if (msgEvent.message?.usage?.input_tokens) {
152
+ inputTokensFromStart = msgEvent.message.usage.input_tokens;
153
+ }
154
+ } else if (event.type === 'content_block_start') {
155
+ const block = (event as any).content_block;
156
+ if (block?.type === 'tool_use') {
157
+ currentToolId = block.id || '';
158
+ currentToolName = block.name || '';
159
+ toolCallAccumulator.set(toolCallIndex, {
160
+ id: currentToolId,
161
+ name: currentToolName,
162
+ arguments: '',
163
+ });
164
+ // Emit early notification so the TUI can show "preparing tool X..."
165
+ yield { done: false, toolCallStart: { id: currentToolId, name: currentToolName } };
166
+ }
167
+ } else if (event.type === 'content_block_delta') {
168
+ if (event.delta.type === 'text_delta') {
169
+ yield { content: event.delta.text, done: false };
170
+ } else if ((event.delta as any).type === 'input_json_delta') {
171
+ const existing = toolCallAccumulator.get(toolCallIndex);
172
+ if (existing) {
173
+ existing.arguments += (event.delta as any).partial_json || '';
174
+ }
175
+ }
176
+ } else if (event.type === 'content_block_stop') {
177
+ if (toolCallAccumulator.has(toolCallIndex) && currentToolId) {
178
+ toolCallIndex++;
179
+ currentToolId = '';
180
+ currentToolName = '';
181
+ }
182
+ } else if (event.type === 'message_delta') {
183
+ const deltaEvent = event as any;
184
+ if (deltaEvent.usage) {
185
+ // message_delta.usage contains output_tokens; input_tokens comes
186
+ // from message_start. Combine both for accurate totals.
187
+ const inputTokens = inputTokensFromStart || deltaEvent.usage.input_tokens || 0;
188
+ const outputTokens = deltaEvent.usage.output_tokens || 0;
189
+ usage = {
190
+ promptTokens: inputTokens,
191
+ completionTokens: outputTokens,
192
+ totalTokens: inputTokens + outputTokens,
193
+ };
194
+ }
195
+ } else if (event.type === 'message_stop') {
196
+ if (!usage) {
197
+ try {
198
+ const finalMessage = await stream.finalMessage();
199
+ usage = {
200
+ promptTokens: finalMessage.usage.input_tokens,
201
+ completionTokens: finalMessage.usage.output_tokens,
202
+ totalTokens: finalMessage.usage.input_tokens + finalMessage.usage.output_tokens,
203
+ };
204
+ } catch {
205
+ /* non-critical */
206
+ }
207
+ }
208
+
209
+ // Emit accumulated tool calls on the final chunk
210
+ const toolCalls =
211
+ toolCallAccumulator.size > 0
212
+ ? Array.from(toolCallAccumulator.values()).map(tc => ({
213
+ id: tc.id,
214
+ type: 'function' as const,
215
+ function: { name: tc.name, arguments: tc.arguments },
216
+ }))
217
+ : undefined;
218
+
219
+ yield { done: true, usage, toolCalls };
220
+ return;
221
+ }
222
+ }
223
+ }
224
+
225
+ async countTokens(text: string): Promise<number> {
226
+ try {
227
+ const response = await this.client.messages.countTokens({
228
+ model: this.defaultModel,
229
+ messages: [{ role: 'user', content: text }],
230
+ });
231
+ return response.input_tokens;
232
+ } catch {
233
+ // Fallback to approximation if API call fails
234
+ return Math.ceil(text.length / 4);
235
+ }
236
+ }
237
+
238
+ getMaxTokens(model: string): number {
239
+ const limits: Record<string, number> = {
240
+ 'claude-sonnet-4-20250514': 8192,
241
+ 'claude-haiku-4-20250514': 8192,
242
+ 'claude-opus-4-20250514': 8192,
243
+ 'claude-3-5-sonnet-20241022': 8192,
244
+ 'claude-3-5-haiku-20241022': 8192,
245
+ };
246
+ return limits[model] || 4096;
247
+ }
248
+
249
+ async listModels(): Promise<string[]> {
250
+ return [
251
+ 'claude-sonnet-4-20250514',
252
+ 'claude-haiku-4-20250514',
253
+ 'claude-opus-4-20250514',
254
+ 'claude-3-5-sonnet-20241022',
255
+ 'claude-3-5-haiku-20241022',
256
+ ];
257
+ }
258
+
259
+ /**
260
+ * Convert messages to Anthropic format
261
+ */
262
+ private convertMessages(messages: LLMMessage[]): Anthropic.MessageParam[] {
263
+ return messages.map(m => {
264
+ if (m.role === 'tool') {
265
+ // Tool result message
266
+ const toolContent =
267
+ typeof m.content === 'string'
268
+ ? m.content
269
+ : Array.isArray(m.content)
270
+ ? m.content
271
+ .filter((b): b is { type: 'text'; text: string } => b.type === 'text')
272
+ .map(b => b.text)
273
+ .join('')
274
+ : '';
275
+ return {
276
+ role: 'user' as const,
277
+ content: [
278
+ {
279
+ type: 'tool_result' as const,
280
+ tool_use_id: m.toolCallId!,
281
+ content: toolContent,
282
+ },
283
+ ],
284
+ };
285
+ }
286
+
287
+ if (m.toolCalls && m.toolCalls.length > 0) {
288
+ // Assistant message with tool calls
289
+ const textContent =
290
+ typeof m.content === 'string'
291
+ ? m.content
292
+ : Array.isArray(m.content)
293
+ ? m.content
294
+ .filter((b): b is { type: 'text'; text: string } => b.type === 'text')
295
+ .map(b => b.text)
296
+ .join('')
297
+ : '';
298
+ return {
299
+ role: 'assistant' as const,
300
+ content: [
301
+ ...(textContent
302
+ ? [
303
+ {
304
+ type: 'text' as const,
305
+ text: textContent,
306
+ },
307
+ ]
308
+ : []),
309
+ ...m.toolCalls.map(tc => {
310
+ try {
311
+ return {
312
+ type: 'tool_use' as const,
313
+ id: tc.id,
314
+ name: tc.function.name,
315
+ input: JSON.parse(tc.function.arguments),
316
+ };
317
+ } catch (error) {
318
+ console.error(
319
+ `Failed to parse tool call arguments for ${tc.function.name}:`,
320
+ error
321
+ );
322
+ return {
323
+ type: 'tool_use' as const,
324
+ id: tc.id,
325
+ name: tc.function.name,
326
+ input: {},
327
+ };
328
+ }
329
+ }),
330
+ ],
331
+ };
332
+ }
333
+
334
+ // Regular message — handle multimodal content arrays
335
+ if (Array.isArray(m.content)) {
336
+ const blocks: Anthropic.ContentBlockParam[] = m.content.map(block => {
337
+ if (block.type === 'image') {
338
+ return {
339
+ type: 'image' as const,
340
+ source: {
341
+ type: 'base64' as const,
342
+ media_type: block.source.media_type,
343
+ data: block.source.data,
344
+ },
345
+ };
346
+ }
347
+ return { type: 'text' as const, text: block.text };
348
+ });
349
+ return {
350
+ role: m.role === 'assistant' ? ('assistant' as const) : ('user' as const),
351
+ content: blocks,
352
+ };
353
+ }
354
+
355
+ return {
356
+ role: m.role === 'assistant' ? ('assistant' as const) : ('user' as const),
357
+ content: m.content,
358
+ };
359
+ });
360
+ }
361
+
362
+ /**
363
+ * Convert tool definitions to Anthropic format
364
+ */
365
+ private convertTools(tools: ToolDefinition[]): Anthropic.Tool[] {
366
+ return tools.map(t => ({
367
+ name: t.function.name,
368
+ description: t.function.description,
369
+ input_schema: {
370
+ ...t.function.parameters,
371
+ type: 'object' as const,
372
+ },
373
+ }));
374
+ }
375
+
376
+ /**
377
+ * Convert tool choice to Anthropic format
378
+ */
379
+ private convertToolChoice(
380
+ toolChoice?: ToolCompletionRequest['toolChoice']
381
+ ): Anthropic.MessageCreateParams['tool_choice'] | undefined {
382
+ if (!toolChoice || toolChoice === 'auto') {
383
+ return { type: 'auto' };
384
+ }
385
+ if (toolChoice === 'none') {
386
+ // When 'none' is specified, don't send tool_choice parameter
387
+ // This prevents Anthropic from using tools
388
+ return undefined;
389
+ }
390
+ if (typeof toolChoice === 'object' && toolChoice.type === 'function') {
391
+ return {
392
+ type: 'tool',
393
+ name: toolChoice.function.name,
394
+ };
395
+ }
396
+ return { type: 'auto' };
397
+ }
398
+
399
+ /**
400
+ * Convert Anthropic response to standard format
401
+ */
402
+ private convertResponse(response: Anthropic.Message): LLMResponse {
403
+ let content = '';
404
+ const toolCalls: ToolCall[] = [];
405
+
406
+ for (const block of response.content) {
407
+ if (block.type === 'text') {
408
+ content += block.text;
409
+ } else if (block.type === 'tool_use') {
410
+ toolCalls.push({
411
+ id: block.id,
412
+ type: 'function',
413
+ function: {
414
+ name: block.name,
415
+ arguments: JSON.stringify(block.input),
416
+ },
417
+ });
418
+ }
419
+ }
420
+
421
+ return {
422
+ content,
423
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
424
+ usage: {
425
+ promptTokens: response.usage.input_tokens,
426
+ completionTokens: response.usage.output_tokens,
427
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens,
428
+ },
429
+ model: response.model,
430
+ finishReason: this.mapFinishReason(response.stop_reason),
431
+ };
432
+ }
433
+ }