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.
Files changed (149) hide show
  1. package/README.md +8 -90
  2. package/dist/agent/agent.d.ts +1 -1
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +8 -2
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/types.d.ts +9 -1
  7. package/dist/agent/types.d.ts.map +1 -1
  8. package/dist/cli/components/AllModelsSelector.d.ts +11 -0
  9. package/dist/cli/components/AllModelsSelector.d.ts.map +1 -0
  10. package/dist/cli/components/AllModelsSelector.js +153 -0
  11. package/dist/cli/components/AllModelsSelector.js.map +1 -0
  12. package/dist/cli/components/App.d.ts.map +1 -1
  13. package/dist/cli/components/App.js +59 -25
  14. package/dist/cli/components/App.js.map +1 -1
  15. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  16. package/dist/cli/components/CommandSuggestions.js +1 -0
  17. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  18. package/dist/cli/components/Messages.d.ts +15 -1
  19. package/dist/cli/components/Messages.d.ts.map +1 -1
  20. package/dist/cli/components/Messages.js +41 -15
  21. package/dist/cli/components/Messages.js.map +1 -1
  22. package/dist/cli/components/ModelSelector.d.ts +7 -7
  23. package/dist/cli/components/ModelSelector.d.ts.map +1 -1
  24. package/dist/cli/components/ModelSelector.js +116 -33
  25. package/dist/cli/components/ModelSelector.js.map +1 -1
  26. package/dist/cli/components/ProviderManager.d.ts +8 -0
  27. package/dist/cli/components/ProviderManager.d.ts.map +1 -0
  28. package/dist/cli/components/ProviderManager.js +280 -0
  29. package/dist/cli/components/ProviderManager.js.map +1 -0
  30. package/dist/cli/components/markdown.d.ts +9 -0
  31. package/dist/cli/components/markdown.d.ts.map +1 -0
  32. package/dist/cli/components/markdown.js +129 -0
  33. package/dist/cli/components/markdown.js.map +1 -0
  34. package/dist/cli/components/theme.d.ts +5 -0
  35. package/dist/cli/components/theme.d.ts.map +1 -1
  36. package/dist/cli/components/theme.js +7 -0
  37. package/dist/cli/components/theme.js.map +1 -1
  38. package/dist/cli/index.js +19 -5
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/config/index.d.ts +3 -2
  41. package/dist/config/index.d.ts.map +1 -1
  42. package/dist/config/index.js +2 -1
  43. package/dist/config/index.js.map +1 -1
  44. package/dist/config/providers-config.d.ts +28 -0
  45. package/dist/config/providers-config.d.ts.map +1 -0
  46. package/dist/config/providers-config.js +79 -0
  47. package/dist/config/providers-config.js.map +1 -0
  48. package/dist/config/types.d.ts +31 -1
  49. package/dist/config/types.d.ts.map +1 -1
  50. package/dist/config/types.js +1 -0
  51. package/dist/config/types.js.map +1 -1
  52. package/dist/providers/gemini.d.ts.map +1 -1
  53. package/dist/providers/gemini.js +14 -3
  54. package/dist/providers/gemini.js.map +1 -1
  55. package/dist/providers/index.d.ts +5 -3
  56. package/dist/providers/index.d.ts.map +1 -1
  57. package/dist/providers/index.js +13 -1
  58. package/dist/providers/index.js.map +1 -1
  59. package/dist/providers/registry.d.ts +66 -0
  60. package/dist/providers/registry.d.ts.map +1 -0
  61. package/dist/providers/registry.js +158 -0
  62. package/dist/providers/registry.js.map +1 -0
  63. package/dist/providers/search/brave.d.ts +14 -0
  64. package/dist/providers/search/brave.d.ts.map +1 -0
  65. package/dist/providers/search/brave.js +87 -0
  66. package/dist/providers/search/brave.js.map +1 -0
  67. package/dist/providers/search/exa.d.ts +12 -0
  68. package/dist/providers/search/exa.d.ts.map +1 -0
  69. package/dist/providers/search/exa.js +158 -0
  70. package/dist/providers/search/exa.js.map +1 -0
  71. package/dist/providers/search/index.d.ts +31 -0
  72. package/dist/providers/search/index.d.ts.map +1 -0
  73. package/dist/providers/search/index.js +75 -0
  74. package/dist/providers/search/index.js.map +1 -0
  75. package/dist/providers/search/serper.d.ts +14 -0
  76. package/dist/providers/search/serper.d.ts.map +1 -0
  77. package/dist/providers/search/serper.js +87 -0
  78. package/dist/providers/search/serper.js.map +1 -0
  79. package/dist/providers/search/types.d.ts +21 -0
  80. package/dist/providers/search/types.d.ts.map +1 -0
  81. package/dist/providers/search/types.js +5 -0
  82. package/dist/providers/search/types.js.map +1 -0
  83. package/dist/providers/store.d.ts +104 -0
  84. package/dist/providers/store.d.ts.map +1 -0
  85. package/dist/providers/store.js +171 -0
  86. package/dist/providers/store.js.map +1 -0
  87. package/dist/providers/types.d.ts +7 -1
  88. package/dist/providers/types.d.ts.map +1 -1
  89. package/dist/providers/vertex-ai.d.ts +33 -0
  90. package/dist/providers/vertex-ai.d.ts.map +1 -0
  91. package/dist/providers/vertex-ai.js +407 -0
  92. package/dist/providers/vertex-ai.js.map +1 -0
  93. package/dist/tools/builtin/webfetch.d.ts +20 -0
  94. package/dist/tools/builtin/webfetch.d.ts.map +1 -0
  95. package/dist/tools/builtin/webfetch.js +231 -0
  96. package/dist/tools/builtin/webfetch.js.map +1 -0
  97. package/dist/tools/builtin/websearch.d.ts +17 -0
  98. package/dist/tools/builtin/websearch.d.ts.map +1 -0
  99. package/dist/tools/builtin/websearch.js +101 -0
  100. package/dist/tools/builtin/websearch.js.map +1 -0
  101. package/dist/tools/index.d.ts +11 -0
  102. package/dist/tools/index.d.ts.map +1 -1
  103. package/dist/tools/index.js +24 -2
  104. package/dist/tools/index.js.map +1 -1
  105. package/dist/tools/types.d.ts +19 -0
  106. package/dist/tools/types.d.ts.map +1 -1
  107. package/dist/tools/types.js +8 -0
  108. package/dist/tools/types.js.map +1 -1
  109. package/dist/tools/utils/ssrf.d.ts +18 -0
  110. package/dist/tools/utils/ssrf.d.ts.map +1 -0
  111. package/dist/tools/utils/ssrf.js +70 -0
  112. package/dist/tools/utils/ssrf.js.map +1 -0
  113. package/docs/README.md +5 -4
  114. package/docs/proposals/0001-web-fetch-tool.md +32 -2
  115. package/docs/proposals/0002-web-search-tool.md +59 -2
  116. package/docs/proposals/0041-configuration-system.md +556 -0
  117. package/docs/proposals/README.md +3 -2
  118. package/docs/providers.md +220 -0
  119. package/package.json +7 -2
  120. package/src/agent/agent.ts +9 -2
  121. package/src/agent/types.ts +9 -1
  122. package/src/cli/components/App.tsx +72 -23
  123. package/src/cli/components/CommandSuggestions.tsx +1 -0
  124. package/src/cli/components/Messages.tsx +117 -29
  125. package/src/cli/components/ModelSelector.tsx +169 -52
  126. package/src/cli/components/ProviderManager.tsx +534 -0
  127. package/src/cli/components/markdown.ts +157 -0
  128. package/src/cli/components/theme.ts +7 -0
  129. package/src/cli/index.tsx +22 -7
  130. package/src/config/index.ts +3 -2
  131. package/src/config/providers-config.ts +85 -0
  132. package/src/config/types.ts +35 -1
  133. package/src/providers/gemini.ts +20 -4
  134. package/src/providers/index.ts +18 -3
  135. package/src/providers/registry.ts +198 -0
  136. package/src/providers/search/brave.ts +132 -0
  137. package/src/providers/search/exa.ts +217 -0
  138. package/src/providers/search/index.ts +79 -0
  139. package/src/providers/search/serper.ts +133 -0
  140. package/src/providers/search/types.ts +24 -0
  141. package/src/providers/store.ts +216 -0
  142. package/src/providers/types.ts +9 -1
  143. package/src/providers/vertex-ai.ts +594 -0
  144. package/src/tools/builtin/webfetch.ts +264 -0
  145. package/src/tools/builtin/websearch.ts +117 -0
  146. package/src/tools/index.ts +24 -2
  147. package/src/tools/types.ts +20 -0
  148. package/src/tools/utils/ssrf.ts +79 -0
  149. 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.0",
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": "npx tsx src/cli/index.tsx",
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
  },
@@ -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
  /**
@@ -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}>{formatTime(s.updatedAt)}</Text>
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
- addHistory({ type: 'info', content: `Model: ${model}` });
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
- addHistory({
354
- type: 'tool_call',
355
- content: event.name,
356
- meta: { toolName: event.name, input: event.input },
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: { toolName: event.name, success: event.result.success },
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
- {!confirmState && !showModelSelector && (
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' },