@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.
- package/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
package/src/llm/types.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base LLM Provider Interface
|
|
3
|
+
* Defines the contract that all LLM providers must implement
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** A text content block inside a multi-part message. */
|
|
7
|
+
export interface TextContentBlock {
|
|
8
|
+
type: 'text';
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** An image content block (base64-encoded). */
|
|
13
|
+
export interface ImageContentBlock {
|
|
14
|
+
type: 'image';
|
|
15
|
+
source: {
|
|
16
|
+
type: 'base64';
|
|
17
|
+
media_type: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';
|
|
18
|
+
data: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A single content block inside a message. */
|
|
23
|
+
export type ContentBlock = TextContentBlock | ImageContentBlock;
|
|
24
|
+
|
|
25
|
+
export interface LLMMessage {
|
|
26
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
27
|
+
/** Plain string for text-only messages, or an array of content blocks for multimodal. */
|
|
28
|
+
content: string | ContentBlock[];
|
|
29
|
+
toolCalls?: ToolCall[];
|
|
30
|
+
toolCallId?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ToolCall {
|
|
35
|
+
id: string;
|
|
36
|
+
type: 'function';
|
|
37
|
+
function: {
|
|
38
|
+
name: string;
|
|
39
|
+
arguments: string; // JSON string
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CompletionRequest {
|
|
44
|
+
messages: LLMMessage[];
|
|
45
|
+
model?: string;
|
|
46
|
+
temperature?: number;
|
|
47
|
+
maxTokens?: number;
|
|
48
|
+
stopSequences?: string[];
|
|
49
|
+
responseFormat?: { type: 'text' | 'json_object' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ToolCompletionRequest extends CompletionRequest {
|
|
53
|
+
tools: ToolDefinition[];
|
|
54
|
+
toolChoice?: 'auto' | 'none' | { type: 'function'; function: { name: string } };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ToolDefinition {
|
|
58
|
+
type: 'function';
|
|
59
|
+
function: {
|
|
60
|
+
name: string;
|
|
61
|
+
description: string;
|
|
62
|
+
parameters: JSONSchema;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface JSONSchema {
|
|
67
|
+
type: string;
|
|
68
|
+
properties?: Record<string, any>;
|
|
69
|
+
required?: string[];
|
|
70
|
+
[key: string]: any;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface LLMResponse {
|
|
74
|
+
content: string;
|
|
75
|
+
toolCalls?: ToolCall[];
|
|
76
|
+
usage: {
|
|
77
|
+
promptTokens: number;
|
|
78
|
+
completionTokens: number;
|
|
79
|
+
totalTokens: number;
|
|
80
|
+
};
|
|
81
|
+
model: string;
|
|
82
|
+
finishReason: 'stop' | 'length' | 'tool_calls' | 'content_filter';
|
|
83
|
+
/** Per-request cost information (added by the router after provider responds) */
|
|
84
|
+
cost?: {
|
|
85
|
+
costUSD: number;
|
|
86
|
+
breakdown: {
|
|
87
|
+
input: number;
|
|
88
|
+
output: number;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface StreamChunk {
|
|
94
|
+
content?: string;
|
|
95
|
+
done: boolean;
|
|
96
|
+
toolCalls?: ToolCall[];
|
|
97
|
+
/** Emitted when a tool call block starts, before arguments are fully assembled.
|
|
98
|
+
* Allows the TUI to show "preparing tool X..." feedback during streaming. */
|
|
99
|
+
toolCallStart?: { id: string; name: string };
|
|
100
|
+
/** Token usage info, typically sent with the final (done) chunk */
|
|
101
|
+
usage?: {
|
|
102
|
+
promptTokens: number;
|
|
103
|
+
completionTokens: number;
|
|
104
|
+
totalTokens: number;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extract the text content from a message's content field.
|
|
110
|
+
* Handles both plain strings and content block arrays.
|
|
111
|
+
*/
|
|
112
|
+
export function getTextContent(content: string | ContentBlock[]): string {
|
|
113
|
+
if (typeof content === 'string') {
|
|
114
|
+
return content;
|
|
115
|
+
}
|
|
116
|
+
return content
|
|
117
|
+
.filter((b): b is TextContentBlock => b.type === 'text')
|
|
118
|
+
.map(b => b.text)
|
|
119
|
+
.join('');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if a message contains image content blocks.
|
|
124
|
+
*/
|
|
125
|
+
export function hasImageContent(content: string | ContentBlock[]): boolean {
|
|
126
|
+
if (typeof content === 'string') {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return content.some(b => b.type === 'image');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Base interface that all LLM providers must implement
|
|
134
|
+
*/
|
|
135
|
+
export interface LLMProvider {
|
|
136
|
+
/** Provider name (e.g., 'anthropic', 'openai', 'google', 'ollama') */
|
|
137
|
+
name: string;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Complete a chat request synchronously
|
|
141
|
+
*/
|
|
142
|
+
complete(request: CompletionRequest): Promise<LLMResponse>;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Stream a chat completion
|
|
146
|
+
*/
|
|
147
|
+
stream(request: CompletionRequest): AsyncIterable<StreamChunk>;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Complete a chat request with tool calling support
|
|
151
|
+
*/
|
|
152
|
+
completeWithTools(request: ToolCompletionRequest): Promise<LLMResponse>;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Stream a chat completion with tool calling support.
|
|
156
|
+
* Text chunks are yielded incrementally; tool calls are accumulated
|
|
157
|
+
* and emitted on the final chunk. Providers that don't implement native
|
|
158
|
+
* streaming-with-tools fall back to the non-streaming completeWithTools.
|
|
159
|
+
*/
|
|
160
|
+
streamWithTools?(request: ToolCompletionRequest): AsyncIterable<StreamChunk>;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Count tokens in a text string
|
|
164
|
+
*/
|
|
165
|
+
countTokens(text: string): Promise<number>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get maximum token limit for a model
|
|
169
|
+
*/
|
|
170
|
+
getMaxTokens(model: string): number;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* List available models for this provider
|
|
174
|
+
*/
|
|
175
|
+
listModels(): Promise<string[]>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Base provider class with common utilities
|
|
180
|
+
*/
|
|
181
|
+
export abstract class BaseProvider implements LLMProvider {
|
|
182
|
+
abstract name: string;
|
|
183
|
+
abstract complete(request: CompletionRequest): Promise<LLMResponse>;
|
|
184
|
+
abstract stream(request: CompletionRequest): AsyncIterable<StreamChunk>;
|
|
185
|
+
abstract completeWithTools(request: ToolCompletionRequest): Promise<LLMResponse>;
|
|
186
|
+
abstract countTokens(text: string): Promise<number>;
|
|
187
|
+
abstract getMaxTokens(model: string): number;
|
|
188
|
+
abstract listModels(): Promise<string[]>;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Extract system prompt from messages
|
|
192
|
+
*/
|
|
193
|
+
protected extractSystemPrompt(messages: LLMMessage[]): string | undefined {
|
|
194
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
195
|
+
if (systemMessages.length === 0) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
return systemMessages.map(m => getTextContent(m.content)).join('\n\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Filter out system messages
|
|
203
|
+
*/
|
|
204
|
+
protected filterSystemMessages(messages: LLMMessage[]): LLMMessage[] {
|
|
205
|
+
return messages.filter(m => m.role !== 'system');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Map finish reason to standard format
|
|
210
|
+
*/
|
|
211
|
+
protected mapFinishReason(reason: string | null | undefined): LLMResponse['finishReason'] {
|
|
212
|
+
if (!reason) {
|
|
213
|
+
return 'stop';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const normalized = reason.toLowerCase();
|
|
217
|
+
if (normalized.includes('stop') || normalized === 'end_turn') {
|
|
218
|
+
return 'stop';
|
|
219
|
+
}
|
|
220
|
+
if (normalized.includes('length') || normalized.includes('max_tokens')) {
|
|
221
|
+
return 'length';
|
|
222
|
+
}
|
|
223
|
+
if (normalized.includes('tool') || normalized.includes('function')) {
|
|
224
|
+
return 'tool_calls';
|
|
225
|
+
}
|
|
226
|
+
if (normalized.includes('content_filter') || normalized.includes('safety')) {
|
|
227
|
+
return 'content_filter';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return 'stop';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Client -- JSON-RPC over stdio
|
|
3
|
+
*
|
|
4
|
+
* Implements the Language Server Protocol client that communicates
|
|
5
|
+
* with language servers via stdin/stdout using JSON-RPC 2.0.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
9
|
+
import { EventEmitter } from 'node:events';
|
|
10
|
+
import type { LanguageConfig } from './languages';
|
|
11
|
+
|
|
12
|
+
/** LSP Diagnostic severity levels. */
|
|
13
|
+
export type DiagnosticSeverity = 1 | 2 | 3 | 4; // Error, Warning, Info, Hint
|
|
14
|
+
|
|
15
|
+
/** A single diagnostic from the language server. */
|
|
16
|
+
export interface Diagnostic {
|
|
17
|
+
file: string;
|
|
18
|
+
line: number;
|
|
19
|
+
column: number;
|
|
20
|
+
endLine?: number;
|
|
21
|
+
endColumn?: number;
|
|
22
|
+
severity: DiagnosticSeverity;
|
|
23
|
+
message: string;
|
|
24
|
+
source?: string;
|
|
25
|
+
code?: string | number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Severity labels for display. */
|
|
29
|
+
const SEVERITY_LABELS: Record<number, string> = {
|
|
30
|
+
1: 'Error',
|
|
31
|
+
2: 'Warning',
|
|
32
|
+
3: 'Info',
|
|
33
|
+
4: 'Hint',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function severityLabel(severity: DiagnosticSeverity): string {
|
|
37
|
+
return SEVERITY_LABELS[severity] ?? 'Unknown';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* LSP Client that communicates with a single language server.
|
|
42
|
+
*/
|
|
43
|
+
export class LSPClient extends EventEmitter {
|
|
44
|
+
private process: ChildProcess | null = null;
|
|
45
|
+
private config: LanguageConfig;
|
|
46
|
+
private requestId = 0;
|
|
47
|
+
private pending = new Map<
|
|
48
|
+
number,
|
|
49
|
+
{ resolve: (value: any) => void; reject: (err: Error) => void }
|
|
50
|
+
>();
|
|
51
|
+
private buffer = '';
|
|
52
|
+
private initialized = false;
|
|
53
|
+
private rootUri: string;
|
|
54
|
+
private diagnostics = new Map<string, Diagnostic[]>();
|
|
55
|
+
|
|
56
|
+
constructor(config: LanguageConfig, rootUri: string) {
|
|
57
|
+
super();
|
|
58
|
+
this.config = config;
|
|
59
|
+
this.rootUri = rootUri;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Whether the client is connected and initialized. */
|
|
63
|
+
get isInitialized(): boolean {
|
|
64
|
+
return this.initialized;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Get the language ID. */
|
|
68
|
+
get languageId(): string {
|
|
69
|
+
return this.config.id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Start the language server process. */
|
|
73
|
+
async start(): Promise<boolean> {
|
|
74
|
+
try {
|
|
75
|
+
this.process = spawn(this.config.command, this.config.args, {
|
|
76
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
77
|
+
env: { ...process.env },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!this.process.stdout || !this.process.stdin) {
|
|
81
|
+
this.process.kill();
|
|
82
|
+
this.process = null;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.process.stdout.on('data', (data: Buffer) => {
|
|
87
|
+
this.handleData(data.toString());
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
this.process.stderr?.on('data', () => {
|
|
91
|
+
// Silently ignore stderr from language servers
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.process.on('exit', () => {
|
|
95
|
+
this.initialized = false;
|
|
96
|
+
this.process = null;
|
|
97
|
+
this.emit('exit');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Send initialize request
|
|
101
|
+
const _initResult = await this.sendRequest('initialize', {
|
|
102
|
+
processId: process.pid,
|
|
103
|
+
rootUri: `file://${this.rootUri}`,
|
|
104
|
+
capabilities: {
|
|
105
|
+
textDocument: {
|
|
106
|
+
synchronization: { didOpen: true, didChange: true, didClose: true },
|
|
107
|
+
publishDiagnostics: { relatedInformation: true },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
initializationOptions: this.config.initializationOptions ?? {},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Send initialized notification
|
|
114
|
+
this.sendNotification('initialized', {});
|
|
115
|
+
this.initialized = true;
|
|
116
|
+
return true;
|
|
117
|
+
} catch {
|
|
118
|
+
this.process = null;
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Stop the language server. */
|
|
124
|
+
async stop(): Promise<void> {
|
|
125
|
+
if (!this.process) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await this.sendRequest('shutdown', null);
|
|
131
|
+
this.sendNotification('exit', null);
|
|
132
|
+
} catch {
|
|
133
|
+
// Server may already be dead
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Force kill after 2 seconds
|
|
137
|
+
const proc = this.process;
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
if (proc && !proc.killed) {
|
|
140
|
+
proc.kill('SIGKILL');
|
|
141
|
+
}
|
|
142
|
+
}, 2000);
|
|
143
|
+
|
|
144
|
+
this.process = null;
|
|
145
|
+
this.initialized = false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Notify the server that a file was opened or changed. */
|
|
149
|
+
async touchFile(filePath: string, content: string, version: number = 1): Promise<void> {
|
|
150
|
+
if (!this.initialized) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const uri = `file://${filePath}`;
|
|
155
|
+
const languageId = this.config.id;
|
|
156
|
+
|
|
157
|
+
// Send didOpen (simplified -- in a full implementation we'd track open state)
|
|
158
|
+
this.sendNotification('textDocument/didOpen', {
|
|
159
|
+
textDocument: { uri, languageId, version, text: content },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Request diagnostics for a file by waiting for publishDiagnostics. */
|
|
164
|
+
async getDiagnostics(filePath: string, timeoutMs: number = 2000): Promise<Diagnostic[]> {
|
|
165
|
+
const uri = `file://${filePath}`;
|
|
166
|
+
|
|
167
|
+
// Return cached diagnostics if available
|
|
168
|
+
const cached = this.diagnostics.get(uri);
|
|
169
|
+
|
|
170
|
+
// Wait for new diagnostics up to timeout
|
|
171
|
+
return new Promise<Diagnostic[]>(resolve => {
|
|
172
|
+
const timer = setTimeout(() => {
|
|
173
|
+
resolve(cached ?? []);
|
|
174
|
+
}, timeoutMs);
|
|
175
|
+
|
|
176
|
+
const handler = (params: any) => {
|
|
177
|
+
if (params.uri === uri) {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
this.removeListener('diagnostics', handler);
|
|
180
|
+
resolve(this.diagnostics.get(uri) ?? []);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
this.on('diagnostics', handler);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Get all cached diagnostics for a file. */
|
|
189
|
+
getCachedDiagnostics(filePath: string): Diagnostic[] {
|
|
190
|
+
return this.diagnostics.get(`file://${filePath}`) ?? [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Send a JSON-RPC request and wait for response. */
|
|
194
|
+
private sendRequest(method: string, params: any): Promise<any> {
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
if (!this.process?.stdin) {
|
|
197
|
+
reject(new Error('LSP process not running'));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const id = ++this.requestId;
|
|
202
|
+
this.pending.set(id, { resolve, reject });
|
|
203
|
+
|
|
204
|
+
const message = JSON.stringify({ jsonrpc: '2.0', id, method, params });
|
|
205
|
+
const header = `Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`;
|
|
206
|
+
this.process.stdin.write(header + message);
|
|
207
|
+
|
|
208
|
+
// Timeout after 10 seconds
|
|
209
|
+
setTimeout(() => {
|
|
210
|
+
if (this.pending.has(id)) {
|
|
211
|
+
this.pending.delete(id);
|
|
212
|
+
reject(new Error(`LSP request timeout: ${method}`));
|
|
213
|
+
}
|
|
214
|
+
}, 10000);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Send a JSON-RPC notification (no response expected). */
|
|
219
|
+
private sendNotification(method: string, params: any): void {
|
|
220
|
+
if (!this.process?.stdin) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const message = JSON.stringify({ jsonrpc: '2.0', method, params });
|
|
225
|
+
const header = `Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`;
|
|
226
|
+
this.process.stdin.write(header + message);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Handle incoming data from the language server. */
|
|
230
|
+
private handleData(data: string): void {
|
|
231
|
+
this.buffer += data;
|
|
232
|
+
|
|
233
|
+
for (;;) {
|
|
234
|
+
const headerEnd = this.buffer.indexOf('\r\n\r\n');
|
|
235
|
+
if (headerEnd === -1) {
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const header = this.buffer.slice(0, headerEnd);
|
|
240
|
+
const contentLengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
241
|
+
if (!contentLengthMatch) {
|
|
242
|
+
this.buffer = this.buffer.slice(headerEnd + 4);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const contentLength = parseInt(contentLengthMatch[1], 10);
|
|
247
|
+
const bodyStart = headerEnd + 4;
|
|
248
|
+
|
|
249
|
+
if (this.buffer.length < bodyStart + contentLength) {
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const body = this.buffer.slice(bodyStart, bodyStart + contentLength);
|
|
254
|
+
this.buffer = this.buffer.slice(bodyStart + contentLength);
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const message = JSON.parse(body);
|
|
258
|
+
this.handleMessage(message);
|
|
259
|
+
} catch {
|
|
260
|
+
// Ignore malformed messages
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Handle a parsed JSON-RPC message. */
|
|
266
|
+
private handleMessage(message: any): void {
|
|
267
|
+
// Response to a request
|
|
268
|
+
if (message.id !== undefined && this.pending.has(message.id)) {
|
|
269
|
+
const handler = this.pending.get(message.id)!;
|
|
270
|
+
this.pending.delete(message.id);
|
|
271
|
+
|
|
272
|
+
if (message.error) {
|
|
273
|
+
handler.reject(new Error(message.error.message));
|
|
274
|
+
} else {
|
|
275
|
+
handler.resolve(message.result);
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Notification from server
|
|
281
|
+
if (message.method === 'textDocument/publishDiagnostics') {
|
|
282
|
+
const { uri, diagnostics: rawDiags } = message.params;
|
|
283
|
+
const parsed: Diagnostic[] = rawDiags.map((d: any) => ({
|
|
284
|
+
file: uri.replace('file://', ''),
|
|
285
|
+
line: (d.range?.start?.line ?? 0) + 1,
|
|
286
|
+
column: (d.range?.start?.character ?? 0) + 1,
|
|
287
|
+
endLine: d.range?.end ? d.range.end.line + 1 : undefined,
|
|
288
|
+
endColumn: d.range?.end ? d.range.end.character + 1 : undefined,
|
|
289
|
+
severity: d.severity ?? 1,
|
|
290
|
+
message: d.message,
|
|
291
|
+
source: d.source,
|
|
292
|
+
code: d.code,
|
|
293
|
+
}));
|
|
294
|
+
this.diagnostics.set(uri, parsed);
|
|
295
|
+
this.emit('diagnostics', message.params);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Server Configurations
|
|
3
|
+
*
|
|
4
|
+
* Defines the LSP binary names, install commands, and file associations
|
|
5
|
+
* for each supported language.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface LanguageConfig {
|
|
9
|
+
/** Language identifier (e.g. 'typescript'). */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Display name. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** File extensions that trigger this language server. */
|
|
14
|
+
extensions: string[];
|
|
15
|
+
/** Command to start the LSP binary. */
|
|
16
|
+
command: string;
|
|
17
|
+
/** Arguments to pass to the LSP binary. */
|
|
18
|
+
args: string[];
|
|
19
|
+
/** Install command hint if binary not found. */
|
|
20
|
+
installHint: string;
|
|
21
|
+
/** Initialization options passed to the LSP server. */
|
|
22
|
+
initializationOptions?: Record<string, unknown>;
|
|
23
|
+
/** Idle timeout in ms before shutting down the server (default: 5 min). */
|
|
24
|
+
idleTimeout?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Supported language server configurations. */
|
|
28
|
+
export const LANGUAGE_CONFIGS: LanguageConfig[] = [
|
|
29
|
+
{
|
|
30
|
+
id: 'typescript',
|
|
31
|
+
name: 'TypeScript',
|
|
32
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'],
|
|
33
|
+
command: 'typescript-language-server',
|
|
34
|
+
args: ['--stdio'],
|
|
35
|
+
installHint: 'npm install -g typescript-language-server typescript',
|
|
36
|
+
initializationOptions: {
|
|
37
|
+
preferences: {
|
|
38
|
+
includeInlayParameterNameHints: 'none',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'go',
|
|
44
|
+
name: 'Go',
|
|
45
|
+
extensions: ['.go'],
|
|
46
|
+
command: 'gopls',
|
|
47
|
+
args: ['serve'],
|
|
48
|
+
installHint: 'go install golang.org/x/tools/gopls@latest',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'python',
|
|
52
|
+
name: 'Python',
|
|
53
|
+
extensions: ['.py', '.pyi'],
|
|
54
|
+
command: 'pylsp',
|
|
55
|
+
args: [],
|
|
56
|
+
installHint: 'pip install python-lsp-server',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'terraform',
|
|
60
|
+
name: 'Terraform/HCL',
|
|
61
|
+
extensions: ['.tf', '.tfvars', '.hcl'],
|
|
62
|
+
command: 'terraform-ls',
|
|
63
|
+
args: ['serve'],
|
|
64
|
+
installHint: 'brew install hashicorp/tap/terraform-ls',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'yaml',
|
|
68
|
+
name: 'YAML',
|
|
69
|
+
extensions: ['.yaml', '.yml'],
|
|
70
|
+
command: 'yaml-language-server',
|
|
71
|
+
args: ['--stdio'],
|
|
72
|
+
installHint: 'npm install -g yaml-language-server',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'docker',
|
|
76
|
+
name: 'Docker',
|
|
77
|
+
extensions: ['Dockerfile', '.dockerfile'],
|
|
78
|
+
command: 'docker-langserver',
|
|
79
|
+
args: ['--stdio'],
|
|
80
|
+
installHint: 'npm install -g dockerfile-language-server-nodejs',
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Find the language config for a given file path.
|
|
86
|
+
* Returns undefined if no matching language server is configured.
|
|
87
|
+
*/
|
|
88
|
+
export function getLanguageForFile(filePath: string): LanguageConfig | undefined {
|
|
89
|
+
const lowerPath = filePath.toLowerCase();
|
|
90
|
+
const baseName = filePath.split('/').pop() ?? '';
|
|
91
|
+
|
|
92
|
+
for (const config of LANGUAGE_CONFIGS) {
|
|
93
|
+
for (const ext of config.extensions) {
|
|
94
|
+
if (ext.startsWith('.')) {
|
|
95
|
+
if (lowerPath.endsWith(ext)) {
|
|
96
|
+
return config;
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// Match exact filename (e.g., Dockerfile)
|
|
100
|
+
if (baseName === ext || baseName.toLowerCase() === ext.toLowerCase()) {
|
|
101
|
+
return config;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get the priority order of language configs.
|
|
112
|
+
* TypeScript > Go > Python > HCL > YAML > Docker
|
|
113
|
+
*/
|
|
114
|
+
export function getLanguagePriority(): LanguageConfig[] {
|
|
115
|
+
return [...LANGUAGE_CONFIGS];
|
|
116
|
+
}
|