gencode-ai 0.1.0 → 0.1.1
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/README.md +8 -90
- package/dist/agent/agent.d.ts +1 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +8 -2
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/types.d.ts +9 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/cli/components/AllModelsSelector.d.ts +11 -0
- package/dist/cli/components/AllModelsSelector.d.ts.map +1 -0
- package/dist/cli/components/AllModelsSelector.js +153 -0
- package/dist/cli/components/AllModelsSelector.js.map +1 -0
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +59 -25
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +1 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Messages.d.ts +15 -1
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +41 -15
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/ModelSelector.d.ts +7 -7
- package/dist/cli/components/ModelSelector.d.ts.map +1 -1
- package/dist/cli/components/ModelSelector.js +116 -33
- package/dist/cli/components/ModelSelector.js.map +1 -1
- package/dist/cli/components/ProviderManager.d.ts +8 -0
- package/dist/cli/components/ProviderManager.d.ts.map +1 -0
- package/dist/cli/components/ProviderManager.js +280 -0
- package/dist/cli/components/ProviderManager.js.map +1 -0
- package/dist/cli/components/markdown.d.ts +9 -0
- package/dist/cli/components/markdown.d.ts.map +1 -0
- package/dist/cli/components/markdown.js +129 -0
- package/dist/cli/components/markdown.js.map +1 -0
- package/dist/cli/components/theme.d.ts +5 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +7 -0
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/cli/index.js +19 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +3 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/providers-config.d.ts +28 -0
- package/dist/config/providers-config.d.ts.map +1 -0
- package/dist/config/providers-config.js +79 -0
- package/dist/config/providers-config.js.map +1 -0
- package/dist/config/types.d.ts +31 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +14 -3
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +5 -3
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +13 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/registry.d.ts +66 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +158 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/search/brave.d.ts +14 -0
- package/dist/providers/search/brave.d.ts.map +1 -0
- package/dist/providers/search/brave.js +87 -0
- package/dist/providers/search/brave.js.map +1 -0
- package/dist/providers/search/exa.d.ts +12 -0
- package/dist/providers/search/exa.d.ts.map +1 -0
- package/dist/providers/search/exa.js +158 -0
- package/dist/providers/search/exa.js.map +1 -0
- package/dist/providers/search/index.d.ts +31 -0
- package/dist/providers/search/index.d.ts.map +1 -0
- package/dist/providers/search/index.js +75 -0
- package/dist/providers/search/index.js.map +1 -0
- package/dist/providers/search/serper.d.ts +14 -0
- package/dist/providers/search/serper.d.ts.map +1 -0
- package/dist/providers/search/serper.js +87 -0
- package/dist/providers/search/serper.js.map +1 -0
- package/dist/providers/search/types.d.ts +21 -0
- package/dist/providers/search/types.d.ts.map +1 -0
- package/dist/providers/search/types.js +5 -0
- package/dist/providers/search/types.js.map +1 -0
- package/dist/providers/store.d.ts +104 -0
- package/dist/providers/store.d.ts.map +1 -0
- package/dist/providers/store.js +171 -0
- package/dist/providers/store.js.map +1 -0
- package/dist/providers/types.d.ts +7 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/vertex-ai.d.ts +33 -0
- package/dist/providers/vertex-ai.d.ts.map +1 -0
- package/dist/providers/vertex-ai.js +407 -0
- package/dist/providers/vertex-ai.js.map +1 -0
- package/dist/tools/builtin/webfetch.d.ts +20 -0
- package/dist/tools/builtin/webfetch.d.ts.map +1 -0
- package/dist/tools/builtin/webfetch.js +231 -0
- package/dist/tools/builtin/webfetch.js.map +1 -0
- package/dist/tools/builtin/websearch.d.ts +17 -0
- package/dist/tools/builtin/websearch.d.ts.map +1 -0
- package/dist/tools/builtin/websearch.js +101 -0
- package/dist/tools/builtin/websearch.js.map +1 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +24 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/types.d.ts +19 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +8 -0
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/utils/ssrf.d.ts +18 -0
- package/dist/tools/utils/ssrf.d.ts.map +1 -0
- package/dist/tools/utils/ssrf.js +70 -0
- package/dist/tools/utils/ssrf.js.map +1 -0
- package/docs/README.md +5 -4
- package/docs/proposals/0001-web-fetch-tool.md +32 -2
- package/docs/proposals/0002-web-search-tool.md +59 -2
- package/docs/proposals/0041-configuration-system.md +556 -0
- package/docs/proposals/README.md +3 -2
- package/docs/providers.md +220 -0
- package/package.json +7 -2
- package/src/agent/agent.ts +9 -2
- package/src/agent/types.ts +9 -1
- package/src/cli/components/App.tsx +72 -23
- package/src/cli/components/CommandSuggestions.tsx +1 -0
- package/src/cli/components/Messages.tsx +117 -29
- package/src/cli/components/ModelSelector.tsx +169 -52
- package/src/cli/components/ProviderManager.tsx +534 -0
- package/src/cli/components/markdown.ts +157 -0
- package/src/cli/components/theme.ts +7 -0
- package/src/cli/index.tsx +22 -7
- package/src/config/index.ts +3 -2
- package/src/config/providers-config.ts +85 -0
- package/src/config/types.ts +35 -1
- package/src/providers/gemini.ts +20 -4
- package/src/providers/index.ts +18 -3
- package/src/providers/registry.ts +198 -0
- package/src/providers/search/brave.ts +132 -0
- package/src/providers/search/exa.ts +217 -0
- package/src/providers/search/index.ts +79 -0
- package/src/providers/search/serper.ts +133 -0
- package/src/providers/search/types.ts +24 -0
- package/src/providers/store.ts +216 -0
- package/src/providers/types.ts +9 -1
- package/src/providers/vertex-ai.ts +594 -0
- package/src/tools/builtin/webfetch.ts +264 -0
- package/src/tools/builtin/websearch.ts +117 -0
- package/src/tools/index.ts +24 -2
- package/src/tools/types.ts +20 -0
- package/src/tools/utils/ssrf.ts +79 -0
- package/CLAUDE.md +0 -70
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Providers
|
|
2
|
+
|
|
3
|
+
GenCode supports multiple LLM providers and Search providers. Use the `/provider` command to manage all provider connections.
|
|
4
|
+
|
|
5
|
+
## LLM Providers
|
|
6
|
+
|
|
7
|
+
### Anthropic (Claude)
|
|
8
|
+
|
|
9
|
+
Claude models from Anthropic with multiple connection options:
|
|
10
|
+
|
|
11
|
+
| Connection Method | Environment Variables | Description |
|
|
12
|
+
|-------------------|----------------------|-------------|
|
|
13
|
+
| API Key | `ANTHROPIC_API_KEY` | Direct API access |
|
|
14
|
+
| Google Vertex AI | `ANTHROPIC_VERTEX_PROJECT_ID` or `GOOGLE_CLOUD_PROJECT` | Claude via GCP |
|
|
15
|
+
| Amazon Bedrock | `AWS_ACCESS_KEY_ID` or `AWS_PROFILE` | Claude via AWS (coming soon) |
|
|
16
|
+
|
|
17
|
+
### OpenAI
|
|
18
|
+
|
|
19
|
+
GPT models from OpenAI:
|
|
20
|
+
|
|
21
|
+
| Connection Method | Environment Variables | Description |
|
|
22
|
+
|-------------------|----------------------|-------------|
|
|
23
|
+
| API Key | `OPENAI_API_KEY` | Direct API access |
|
|
24
|
+
|
|
25
|
+
### Google Gemini
|
|
26
|
+
|
|
27
|
+
Gemini models from Google:
|
|
28
|
+
|
|
29
|
+
| Connection Method | Environment Variables | Description |
|
|
30
|
+
|-------------------|----------------------|-------------|
|
|
31
|
+
| API Key | `GOOGLE_API_KEY` or `GEMINI_API_KEY` | Direct API access |
|
|
32
|
+
|
|
33
|
+
## Search Providers
|
|
34
|
+
|
|
35
|
+
GenCode includes a WebSearch tool that can search the web for current information. Multiple search providers are supported:
|
|
36
|
+
|
|
37
|
+
### Exa AI (Default)
|
|
38
|
+
|
|
39
|
+
AI-native search engine using Exa's public MCP endpoint. Works out of the box without any configuration.
|
|
40
|
+
|
|
41
|
+
| Connection Method | Environment Variables | Description |
|
|
42
|
+
|-------------------|----------------------|-------------|
|
|
43
|
+
| Public MCP | (none required) | Uses public `mcp.exa.ai` endpoint |
|
|
44
|
+
|
|
45
|
+
**Features:**
|
|
46
|
+
- No configuration required - works immediately
|
|
47
|
+
- AI-optimized search results
|
|
48
|
+
- Live crawl support for fresh content
|
|
49
|
+
|
|
50
|
+
**Note:** Uses Exa's public MCP endpoint. For heavy usage, consider using Serper or Brave with your own API key.
|
|
51
|
+
|
|
52
|
+
### Serper.dev
|
|
53
|
+
|
|
54
|
+
Google Search results via Serper API.
|
|
55
|
+
|
|
56
|
+
| Connection Method | Environment Variables | Description |
|
|
57
|
+
|-------------------|----------------------|-------------|
|
|
58
|
+
| API Key | `SERPER_API_KEY` | Google Search via Serper |
|
|
59
|
+
|
|
60
|
+
**Features:**
|
|
61
|
+
- 2,500 free queries (no credit card required)
|
|
62
|
+
- Google Search quality results
|
|
63
|
+
- Fast response times
|
|
64
|
+
|
|
65
|
+
**Get API Key:** https://serper.dev
|
|
66
|
+
|
|
67
|
+
### Brave Search
|
|
68
|
+
|
|
69
|
+
Privacy-focused search from Brave.
|
|
70
|
+
|
|
71
|
+
| Connection Method | Environment Variables | Description |
|
|
72
|
+
|-------------------|----------------------|-------------|
|
|
73
|
+
| API Key | `BRAVE_API_KEY` | Privacy-focused search |
|
|
74
|
+
|
|
75
|
+
**Features:**
|
|
76
|
+
- 2,000 free queries per month
|
|
77
|
+
- Privacy-first approach
|
|
78
|
+
- Independent search index
|
|
79
|
+
|
|
80
|
+
**Get API Key:** https://brave.com/search/api
|
|
81
|
+
|
|
82
|
+
### Search Provider Priority
|
|
83
|
+
|
|
84
|
+
When multiple search providers are configured, the priority is:
|
|
85
|
+
|
|
86
|
+
1. **Configured in `/provider`** - Explicitly selected provider
|
|
87
|
+
2. **Environment variables** - `SERPER_API_KEY` > `BRAVE_API_KEY`
|
|
88
|
+
3. **Default** - Exa AI (no configuration needed)
|
|
89
|
+
|
|
90
|
+
## Commands
|
|
91
|
+
|
|
92
|
+
### `/provider` - Provider Management
|
|
93
|
+
|
|
94
|
+
Opens the provider management interface with two tabs:
|
|
95
|
+
|
|
96
|
+
**[L] LLM Providers Tab:**
|
|
97
|
+
- View connected and available LLM providers
|
|
98
|
+
- Connect to new providers
|
|
99
|
+
- Refresh model lists for connected providers
|
|
100
|
+
- Remove provider connections
|
|
101
|
+
|
|
102
|
+
**[S] Search Providers Tab:**
|
|
103
|
+
- View available search providers
|
|
104
|
+
- Select which search provider to use for WebSearch
|
|
105
|
+
- See configuration status (configured/not configured)
|
|
106
|
+
|
|
107
|
+
**Keyboard shortcuts:**
|
|
108
|
+
- `Tab` or `L`/`S` - Switch between tabs
|
|
109
|
+
- `↑↓` - Navigate between providers
|
|
110
|
+
- `Enter` - Connect/Select provider
|
|
111
|
+
- `r` - Remove a connected LLM provider
|
|
112
|
+
- `Esc` - Exit
|
|
113
|
+
|
|
114
|
+
### `/model` - Model Selection
|
|
115
|
+
|
|
116
|
+
Opens the model selector to switch between models from all connected providers:
|
|
117
|
+
|
|
118
|
+
- Models are grouped by provider
|
|
119
|
+
- Shows cached models (fast, no API call)
|
|
120
|
+
- Supports fuzzy search filtering
|
|
121
|
+
|
|
122
|
+
**Keyboard shortcuts:**
|
|
123
|
+
- `↑↓` - Navigate between models
|
|
124
|
+
- `Enter` - Select model
|
|
125
|
+
- `Esc` - Cancel
|
|
126
|
+
|
|
127
|
+
## Google Vertex AI Setup
|
|
128
|
+
|
|
129
|
+
To use Claude models via Google Vertex AI:
|
|
130
|
+
|
|
131
|
+
### 1. Set Environment Variables
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Required: Your GCP project ID
|
|
135
|
+
export ANTHROPIC_VERTEX_PROJECT_ID="your-project-id"
|
|
136
|
+
|
|
137
|
+
# Optional: Region (defaults to us-east5)
|
|
138
|
+
export ANTHROPIC_VERTEX_REGION="us-east5"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Alternative variables also supported:
|
|
142
|
+
- `GOOGLE_CLOUD_PROJECT` - GCP project ID
|
|
143
|
+
- `CLOUD_ML_REGION` - GCP region
|
|
144
|
+
|
|
145
|
+
### 2. Authenticate with Google Cloud
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Login and set up Application Default Credentials
|
|
149
|
+
gcloud auth application-default login
|
|
150
|
+
|
|
151
|
+
# Verify authentication
|
|
152
|
+
gcloud auth application-default print-access-token
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 3. Enable Vertex AI API
|
|
156
|
+
|
|
157
|
+
Ensure the Vertex AI API is enabled in your GCP project:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
gcloud services enable aiplatform.googleapis.com
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 4. Connect in GenCode
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
/provider
|
|
167
|
+
# Select "Anthropic"
|
|
168
|
+
# Choose "Google Vertex AI" connection method
|
|
169
|
+
# Press Enter to connect
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Configuration Storage
|
|
173
|
+
|
|
174
|
+
Provider connections and cached models are stored in:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
~/.gencode/
|
|
178
|
+
├── settings.json # Current model and provider settings
|
|
179
|
+
└── providers.json # Provider connections and model cache
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### providers.json Structure
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"connections": {
|
|
187
|
+
"anthropic": {
|
|
188
|
+
"method": "vertex",
|
|
189
|
+
"connectedAt": "2025-01-15T10:00:00Z"
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"models": {
|
|
193
|
+
"anthropic": {
|
|
194
|
+
"cachedAt": "2025-01-15T10:00:00Z",
|
|
195
|
+
"list": [
|
|
196
|
+
{ "id": "claude-sonnet-4-5@20250929", "name": "Claude Sonnet 4.5" }
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"searchProvider": "exa"
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The `searchProvider` field stores the selected search provider. Valid values: `exa`, `serper`, `brave`. If not set, defaults to `exa`.
|
|
205
|
+
|
|
206
|
+
## Troubleshooting
|
|
207
|
+
|
|
208
|
+
### "Not configured" status
|
|
209
|
+
|
|
210
|
+
If a provider shows "(not configured)", set the required environment variables and restart GenCode.
|
|
211
|
+
|
|
212
|
+
### Model list is empty
|
|
213
|
+
|
|
214
|
+
Use `/provider` and press Enter on a connected provider to refresh the model cache.
|
|
215
|
+
|
|
216
|
+
### Vertex AI authentication errors
|
|
217
|
+
|
|
218
|
+
1. Verify you're logged in: `gcloud auth application-default print-access-token`
|
|
219
|
+
2. Check project ID: `echo $ANTHROPIC_VERTEX_PROJECT_ID`
|
|
220
|
+
3. Ensure Vertex AI API is enabled in your project
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gencode-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "GenCode - An open-source, provider-agnostic AI coding assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"dev": "tsc --watch",
|
|
14
|
-
"start": "
|
|
14
|
+
"start": "node dist/cli/index.js",
|
|
15
|
+
"start:dev": "npx tsx src/cli/index.tsx",
|
|
15
16
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
16
17
|
"example": "npx tsx examples/basic.ts"
|
|
17
18
|
},
|
|
@@ -28,16 +29,20 @@
|
|
|
28
29
|
"dependencies": {
|
|
29
30
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
30
31
|
"@google/generative-ai": "^0.24.1",
|
|
32
|
+
"@types/turndown": "^5.0.6",
|
|
31
33
|
"chalk": "^5.6.2",
|
|
32
34
|
"dotenv": "^17.2.3",
|
|
33
35
|
"fast-glob": "^3.3.3",
|
|
34
36
|
"glob": "^13.0.0",
|
|
37
|
+
"google-auth-library": "^10.5.0",
|
|
35
38
|
"ink": "^6.6.0",
|
|
36
39
|
"ink-spinner": "^5.0.0",
|
|
37
40
|
"ink-text-input": "^6.0.0",
|
|
41
|
+
"marked": "^17.0.1",
|
|
38
42
|
"openai": "^6.16.0",
|
|
39
43
|
"ora": "^9.0.0",
|
|
40
44
|
"react": "^19.2.3",
|
|
45
|
+
"turndown": "^7.2.2",
|
|
41
46
|
"undici": "^7.18.2",
|
|
42
47
|
"zod": "^4.3.5"
|
|
43
48
|
},
|
package/src/agent/agent.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { LLMProvider, Message, ToolResultContent } from '../providers/types.js';
|
|
6
|
-
import { createProvider } from '../providers/index.js';
|
|
6
|
+
import { createProvider, inferProvider } from '../providers/index.js';
|
|
7
7
|
import { ToolRegistry, createDefaultRegistry } from '../tools/index.js';
|
|
8
8
|
import { PermissionManager } from '../permissions/index.js';
|
|
9
9
|
import { SessionManager } from '../session/index.js';
|
|
@@ -64,10 +64,17 @@ export class Agent {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Set the model to use
|
|
67
|
+
* Set the model to use (auto-switches provider if needed)
|
|
68
68
|
*/
|
|
69
69
|
setModel(model: string): void {
|
|
70
70
|
this.config.model = model;
|
|
71
|
+
|
|
72
|
+
// Auto-switch provider based on model name
|
|
73
|
+
const newProvider = inferProvider(model);
|
|
74
|
+
if (newProvider !== this.config.provider) {
|
|
75
|
+
this.config.provider = newProvider;
|
|
76
|
+
this.provider = createProvider({ provider: newProvider });
|
|
77
|
+
}
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
/**
|
package/src/agent/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { PermissionConfig } from '../permissions/types.js';
|
|
6
6
|
|
|
7
7
|
export interface AgentConfig {
|
|
8
|
-
provider: 'openai' | 'anthropic' | 'gemini';
|
|
8
|
+
provider: 'openai' | 'anthropic' | 'gemini' | 'vertex-ai';
|
|
9
9
|
model: string;
|
|
10
10
|
systemPrompt?: string;
|
|
11
11
|
tools?: string[];
|
|
@@ -35,6 +35,14 @@ export interface AgentEventToolResult {
|
|
|
35
35
|
success: boolean;
|
|
36
36
|
output: string;
|
|
37
37
|
error?: string;
|
|
38
|
+
metadata?: {
|
|
39
|
+
title?: string;
|
|
40
|
+
subtitle?: string;
|
|
41
|
+
size?: number;
|
|
42
|
+
statusCode?: number;
|
|
43
|
+
contentType?: string;
|
|
44
|
+
duration?: number;
|
|
45
|
+
};
|
|
38
46
|
};
|
|
39
47
|
}
|
|
40
48
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
AssistantMessage,
|
|
12
12
|
ToolCall,
|
|
13
13
|
ToolResult,
|
|
14
|
+
PendingToolCall,
|
|
14
15
|
InfoMessage,
|
|
15
16
|
WelcomeMessage,
|
|
16
17
|
CompletionMessage,
|
|
@@ -19,8 +20,10 @@ import { Header } from './Header.js';
|
|
|
19
20
|
import { ProgressBar } from './Spinner.js';
|
|
20
21
|
import { PromptInput, ConfirmPrompt } from './Input.js';
|
|
21
22
|
import { ModelSelector } from './ModelSelector.js';
|
|
23
|
+
import { ProviderManager } from './ProviderManager.js';
|
|
22
24
|
import { CommandSuggestions, getFilteredCommands } from './CommandSuggestions.js';
|
|
23
25
|
import { colors, icons } from './theme.js';
|
|
26
|
+
import type { ProviderName } from '../../providers/index.js';
|
|
24
27
|
|
|
25
28
|
// Types
|
|
26
29
|
interface HistoryItem {
|
|
@@ -60,12 +63,28 @@ function useAgent(config: AgentConfig) {
|
|
|
60
63
|
return agent;
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Utils
|
|
68
|
+
// ============================================================================
|
|
69
|
+
const genId = () => Math.random().toString(36).slice(2);
|
|
70
|
+
|
|
71
|
+
const formatRelativeTime = (dateStr: string) => {
|
|
72
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
73
|
+
const mins = Math.floor(diff / 60000);
|
|
74
|
+
const hrs = Math.floor(mins / 60);
|
|
75
|
+
const days = Math.floor(hrs / 24);
|
|
76
|
+
if (mins < 60) return `${mins}m`;
|
|
77
|
+
if (hrs < 24) return `${hrs}h`;
|
|
78
|
+
return `${days}d`;
|
|
79
|
+
};
|
|
80
|
+
|
|
63
81
|
// ============================================================================
|
|
64
82
|
// Help Component
|
|
65
83
|
// ============================================================================
|
|
66
84
|
function HelpPanel() {
|
|
67
85
|
const commands: [string, string][] = [
|
|
68
86
|
['/model [name]', 'Switch model'],
|
|
87
|
+
['/provider', 'Manage providers'],
|
|
69
88
|
['/sessions', 'List sessions'],
|
|
70
89
|
['/resume [n]', 'Resume session'],
|
|
71
90
|
['/new', 'New session'],
|
|
@@ -92,16 +111,6 @@ interface SessionsTableProps {
|
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
function SessionsTable({ sessions }: SessionsTableProps) {
|
|
95
|
-
const formatTime = (dateStr: string) => {
|
|
96
|
-
const diff = Date.now() - new Date(dateStr).getTime();
|
|
97
|
-
const mins = Math.floor(diff / 60000);
|
|
98
|
-
const hrs = Math.floor(mins / 60);
|
|
99
|
-
const days = Math.floor(hrs / 24);
|
|
100
|
-
if (mins < 60) return `${mins}m`;
|
|
101
|
-
if (hrs < 24) return `${hrs}h`;
|
|
102
|
-
return `${days}d`;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
114
|
return (
|
|
106
115
|
<Box flexDirection="column">
|
|
107
116
|
{sessions.slice(0, 6).map((s, i) => (
|
|
@@ -109,7 +118,7 @@ function SessionsTable({ sessions }: SessionsTableProps) {
|
|
|
109
118
|
<Text color={colors.textMuted}>{String(i + 1).padEnd(2)}</Text>
|
|
110
119
|
<Text color={colors.primary}>{s.id.slice(0, 7).padEnd(8)}</Text>
|
|
111
120
|
<Text>{s.title.slice(0, 25).padEnd(26)}</Text>
|
|
112
|
-
<Text color={colors.textMuted}>{
|
|
121
|
+
<Text color={colors.textMuted}>{formatRelativeTime(s.updatedAt)}</Text>
|
|
113
122
|
</Text>
|
|
114
123
|
))}
|
|
115
124
|
</Box>
|
|
@@ -123,9 +132,6 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
|
|
|
123
132
|
const { exit } = useApp();
|
|
124
133
|
const agent = useAgent(config);
|
|
125
134
|
|
|
126
|
-
// Generate unique ID
|
|
127
|
-
const genId = () => Math.random().toString(36).slice(2);
|
|
128
|
-
|
|
129
135
|
// Initial header item
|
|
130
136
|
const cwd = config.cwd || process.cwd();
|
|
131
137
|
const home = process.env.HOME || '';
|
|
@@ -156,9 +162,12 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
|
|
|
156
162
|
const streamingTextRef = useRef(''); // Track current streaming text for closure
|
|
157
163
|
const [confirmState, setConfirmState] = useState<ConfirmState | null>(null);
|
|
158
164
|
const [showModelSelector, setShowModelSelector] = useState(false);
|
|
165
|
+
const [showProviderManager, setShowProviderManager] = useState(false);
|
|
159
166
|
const [currentModel, setCurrentModel] = useState(config.model);
|
|
160
167
|
const [cmdSuggestionIndex, setCmdSuggestionIndex] = useState(0);
|
|
161
168
|
const [inputKey, setInputKey] = useState(0); // Force cursor to end after autocomplete
|
|
169
|
+
const [pendingTool, setPendingTool] = useState<{ name: string; input: Record<string, unknown> } | null>(null);
|
|
170
|
+
const pendingToolRef = useRef<{ name: string; input: Record<string, unknown> } | null>(null);
|
|
162
171
|
|
|
163
172
|
// Check if showing command suggestions
|
|
164
173
|
const showCmdSuggestions = input.startsWith('/') && !isProcessing;
|
|
@@ -202,11 +211,16 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
|
|
|
202
211
|
};
|
|
203
212
|
|
|
204
213
|
// Handle model selection
|
|
205
|
-
const handleModelSelect = async (model: string) => {
|
|
214
|
+
const handleModelSelect = async (model: string, providerId?: ProviderName) => {
|
|
206
215
|
agent.setModel(model);
|
|
207
216
|
setCurrentModel(model);
|
|
208
217
|
setShowModelSelector(false);
|
|
209
|
-
|
|
218
|
+
|
|
219
|
+
if (providerId) {
|
|
220
|
+
addHistory({ type: 'info', content: `${providerId}: ${model}` });
|
|
221
|
+
} else {
|
|
222
|
+
addHistory({ type: 'info', content: `Model: ${model}` });
|
|
223
|
+
}
|
|
210
224
|
|
|
211
225
|
// Save to settings for next startup
|
|
212
226
|
if (settingsManager) {
|
|
@@ -302,6 +316,11 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
|
|
|
302
316
|
return true;
|
|
303
317
|
}
|
|
304
318
|
|
|
319
|
+
case 'provider': {
|
|
320
|
+
setShowProviderManager(true);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
305
324
|
case 'init': {
|
|
306
325
|
addHistory({ type: 'info', content: '/init command not available in this version' });
|
|
307
326
|
return true;
|
|
@@ -350,19 +369,33 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
|
|
|
350
369
|
streamingTextRef.current = '';
|
|
351
370
|
setStreamingText('');
|
|
352
371
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
});
|
|
372
|
+
// Set pending tool for spinner animation (use both state and ref)
|
|
373
|
+
const toolInfo = { name: event.name, input: event.input as Record<string, unknown> };
|
|
374
|
+
pendingToolRef.current = toolInfo;
|
|
375
|
+
setPendingTool(toolInfo);
|
|
358
376
|
break;
|
|
359
377
|
|
|
360
378
|
case 'tool_result':
|
|
379
|
+
// Add tool_call to history (now completed) - use ref for correct value
|
|
380
|
+
if (pendingToolRef.current) {
|
|
381
|
+
addHistory({
|
|
382
|
+
type: 'tool_call',
|
|
383
|
+
content: pendingToolRef.current.name,
|
|
384
|
+
meta: { toolName: pendingToolRef.current.name, input: pendingToolRef.current.input },
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
// Add tool_result to history
|
|
361
388
|
addHistory({
|
|
362
389
|
type: 'tool_result',
|
|
363
390
|
content: event.result.output,
|
|
364
|
-
meta: {
|
|
391
|
+
meta: {
|
|
392
|
+
toolName: event.name,
|
|
393
|
+
success: event.result.success,
|
|
394
|
+
metadata: event.result.metadata,
|
|
395
|
+
},
|
|
365
396
|
});
|
|
397
|
+
pendingToolRef.current = null;
|
|
398
|
+
setPendingTool(null);
|
|
366
399
|
setIsThinking(true);
|
|
367
400
|
break;
|
|
368
401
|
|
|
@@ -496,6 +529,7 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
|
|
|
496
529
|
name={(item.meta?.toolName as string) || ''}
|
|
497
530
|
success={(item.meta?.success as boolean) ?? true}
|
|
498
531
|
output={item.content}
|
|
532
|
+
metadata={item.meta?.metadata as Record<string, unknown> | undefined}
|
|
499
533
|
/>
|
|
500
534
|
);
|
|
501
535
|
case 'info':
|
|
@@ -517,6 +551,8 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
|
|
|
517
551
|
{(item) => <Box key={item.id}>{renderHistoryItem(item)}</Box>}
|
|
518
552
|
</Static>
|
|
519
553
|
|
|
554
|
+
{pendingTool && <PendingToolCall name={pendingTool.name} input={pendingTool.input} />}
|
|
555
|
+
|
|
520
556
|
{streamingText && <AssistantMessage text={streamingText} streaming />}
|
|
521
557
|
|
|
522
558
|
{confirmState && (
|
|
@@ -539,7 +575,20 @@ export function App({ config, settingsManager, resumeLatest }: AppProps) {
|
|
|
539
575
|
</Box>
|
|
540
576
|
)}
|
|
541
577
|
|
|
542
|
-
{
|
|
578
|
+
{showProviderManager && (
|
|
579
|
+
<Box marginTop={1}>
|
|
580
|
+
<ProviderManager
|
|
581
|
+
onClose={() => setShowProviderManager(false)}
|
|
582
|
+
onProviderChange={(providerId, model) => {
|
|
583
|
+
agent.setModel(model);
|
|
584
|
+
setCurrentModel(model);
|
|
585
|
+
addHistory({ type: 'info', content: `Switched to ${providerId}: ${model}` });
|
|
586
|
+
}}
|
|
587
|
+
/>
|
|
588
|
+
</Box>
|
|
589
|
+
)}
|
|
590
|
+
|
|
591
|
+
{!confirmState && !showModelSelector && !showProviderManager && (
|
|
543
592
|
<Box flexDirection="column" marginTop={1}>
|
|
544
593
|
<PromptInput
|
|
545
594
|
key={inputKey}
|
|
@@ -8,6 +8,7 @@ interface Command {
|
|
|
8
8
|
|
|
9
9
|
export const COMMANDS: Command[] = [
|
|
10
10
|
{ name: '/model', description: 'Switch model' },
|
|
11
|
+
{ name: '/provider', description: 'Manage providers' },
|
|
11
12
|
{ name: '/sessions', description: 'List sessions' },
|
|
12
13
|
{ name: '/resume', description: 'Resume session' },
|
|
13
14
|
{ name: '/new', description: 'New session' },
|