tunacode-cli 0.1.21__py3-none-any.whl
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.
Potentially problematic release.
This version of tunacode-cli might be problematic. Click here for more details.
- tunacode/__init__.py +0 -0
- tunacode/cli/textual_repl.tcss +283 -0
- tunacode/configuration/__init__.py +1 -0
- tunacode/configuration/defaults.py +45 -0
- tunacode/configuration/models.py +147 -0
- tunacode/configuration/models_registry.json +1 -0
- tunacode/configuration/pricing.py +74 -0
- tunacode/configuration/settings.py +35 -0
- tunacode/constants.py +227 -0
- tunacode/core/__init__.py +6 -0
- tunacode/core/agents/__init__.py +39 -0
- tunacode/core/agents/agent_components/__init__.py +48 -0
- tunacode/core/agents/agent_components/agent_config.py +441 -0
- tunacode/core/agents/agent_components/agent_helpers.py +290 -0
- tunacode/core/agents/agent_components/message_handler.py +99 -0
- tunacode/core/agents/agent_components/node_processor.py +477 -0
- tunacode/core/agents/agent_components/response_state.py +129 -0
- tunacode/core/agents/agent_components/result_wrapper.py +51 -0
- tunacode/core/agents/agent_components/state_transition.py +112 -0
- tunacode/core/agents/agent_components/streaming.py +271 -0
- tunacode/core/agents/agent_components/task_completion.py +40 -0
- tunacode/core/agents/agent_components/tool_buffer.py +44 -0
- tunacode/core/agents/agent_components/tool_executor.py +101 -0
- tunacode/core/agents/agent_components/truncation_checker.py +37 -0
- tunacode/core/agents/delegation_tools.py +109 -0
- tunacode/core/agents/main.py +545 -0
- tunacode/core/agents/prompts.py +66 -0
- tunacode/core/agents/research_agent.py +231 -0
- tunacode/core/compaction.py +218 -0
- tunacode/core/prompting/__init__.py +27 -0
- tunacode/core/prompting/loader.py +66 -0
- tunacode/core/prompting/prompting_engine.py +98 -0
- tunacode/core/prompting/sections.py +50 -0
- tunacode/core/prompting/templates.py +69 -0
- tunacode/core/state.py +409 -0
- tunacode/exceptions.py +313 -0
- tunacode/indexing/__init__.py +5 -0
- tunacode/indexing/code_index.py +432 -0
- tunacode/indexing/constants.py +86 -0
- tunacode/lsp/__init__.py +112 -0
- tunacode/lsp/client.py +351 -0
- tunacode/lsp/diagnostics.py +19 -0
- tunacode/lsp/servers.py +101 -0
- tunacode/prompts/default_prompt.md +952 -0
- tunacode/prompts/research/sections/agent_role.xml +5 -0
- tunacode/prompts/research/sections/constraints.xml +14 -0
- tunacode/prompts/research/sections/output_format.xml +57 -0
- tunacode/prompts/research/sections/tool_use.xml +23 -0
- tunacode/prompts/sections/advanced_patterns.xml +255 -0
- tunacode/prompts/sections/agent_role.xml +8 -0
- tunacode/prompts/sections/completion.xml +10 -0
- tunacode/prompts/sections/critical_rules.xml +37 -0
- tunacode/prompts/sections/examples.xml +220 -0
- tunacode/prompts/sections/output_style.xml +94 -0
- tunacode/prompts/sections/parallel_exec.xml +105 -0
- tunacode/prompts/sections/search_pattern.xml +100 -0
- tunacode/prompts/sections/system_info.xml +6 -0
- tunacode/prompts/sections/tool_use.xml +84 -0
- tunacode/prompts/sections/user_instructions.xml +3 -0
- tunacode/py.typed +0 -0
- tunacode/templates/__init__.py +5 -0
- tunacode/templates/loader.py +15 -0
- tunacode/tools/__init__.py +10 -0
- tunacode/tools/authorization/__init__.py +29 -0
- tunacode/tools/authorization/context.py +32 -0
- tunacode/tools/authorization/factory.py +20 -0
- tunacode/tools/authorization/handler.py +58 -0
- tunacode/tools/authorization/notifier.py +35 -0
- tunacode/tools/authorization/policy.py +19 -0
- tunacode/tools/authorization/requests.py +119 -0
- tunacode/tools/authorization/rules.py +72 -0
- tunacode/tools/bash.py +222 -0
- tunacode/tools/decorators.py +213 -0
- tunacode/tools/glob.py +353 -0
- tunacode/tools/grep.py +468 -0
- tunacode/tools/grep_components/__init__.py +9 -0
- tunacode/tools/grep_components/file_filter.py +93 -0
- tunacode/tools/grep_components/pattern_matcher.py +158 -0
- tunacode/tools/grep_components/result_formatter.py +87 -0
- tunacode/tools/grep_components/search_result.py +34 -0
- tunacode/tools/list_dir.py +205 -0
- tunacode/tools/prompts/bash_prompt.xml +10 -0
- tunacode/tools/prompts/glob_prompt.xml +7 -0
- tunacode/tools/prompts/grep_prompt.xml +10 -0
- tunacode/tools/prompts/list_dir_prompt.xml +7 -0
- tunacode/tools/prompts/read_file_prompt.xml +9 -0
- tunacode/tools/prompts/todoclear_prompt.xml +12 -0
- tunacode/tools/prompts/todoread_prompt.xml +16 -0
- tunacode/tools/prompts/todowrite_prompt.xml +28 -0
- tunacode/tools/prompts/update_file_prompt.xml +9 -0
- tunacode/tools/prompts/web_fetch_prompt.xml +11 -0
- tunacode/tools/prompts/write_file_prompt.xml +7 -0
- tunacode/tools/react.py +111 -0
- tunacode/tools/read_file.py +68 -0
- tunacode/tools/todo.py +222 -0
- tunacode/tools/update_file.py +62 -0
- tunacode/tools/utils/__init__.py +1 -0
- tunacode/tools/utils/ripgrep.py +311 -0
- tunacode/tools/utils/text_match.py +352 -0
- tunacode/tools/web_fetch.py +245 -0
- tunacode/tools/write_file.py +34 -0
- tunacode/tools/xml_helper.py +34 -0
- tunacode/types/__init__.py +166 -0
- tunacode/types/base.py +94 -0
- tunacode/types/callbacks.py +53 -0
- tunacode/types/dataclasses.py +121 -0
- tunacode/types/pydantic_ai.py +31 -0
- tunacode/types/state.py +122 -0
- tunacode/ui/__init__.py +6 -0
- tunacode/ui/app.py +542 -0
- tunacode/ui/commands/__init__.py +430 -0
- tunacode/ui/components/__init__.py +1 -0
- tunacode/ui/headless/__init__.py +5 -0
- tunacode/ui/headless/output.py +72 -0
- tunacode/ui/main.py +252 -0
- tunacode/ui/renderers/__init__.py +41 -0
- tunacode/ui/renderers/errors.py +197 -0
- tunacode/ui/renderers/panels.py +550 -0
- tunacode/ui/renderers/search.py +314 -0
- tunacode/ui/renderers/tools/__init__.py +21 -0
- tunacode/ui/renderers/tools/bash.py +247 -0
- tunacode/ui/renderers/tools/diagnostics.py +186 -0
- tunacode/ui/renderers/tools/glob.py +226 -0
- tunacode/ui/renderers/tools/grep.py +228 -0
- tunacode/ui/renderers/tools/list_dir.py +198 -0
- tunacode/ui/renderers/tools/read_file.py +226 -0
- tunacode/ui/renderers/tools/research.py +294 -0
- tunacode/ui/renderers/tools/update_file.py +237 -0
- tunacode/ui/renderers/tools/web_fetch.py +182 -0
- tunacode/ui/repl_support.py +226 -0
- tunacode/ui/screens/__init__.py +16 -0
- tunacode/ui/screens/model_picker.py +303 -0
- tunacode/ui/screens/session_picker.py +181 -0
- tunacode/ui/screens/setup.py +218 -0
- tunacode/ui/screens/theme_picker.py +90 -0
- tunacode/ui/screens/update_confirm.py +69 -0
- tunacode/ui/shell_runner.py +129 -0
- tunacode/ui/styles/layout.tcss +98 -0
- tunacode/ui/styles/modals.tcss +38 -0
- tunacode/ui/styles/panels.tcss +81 -0
- tunacode/ui/styles/theme-nextstep.tcss +303 -0
- tunacode/ui/styles/widgets.tcss +33 -0
- tunacode/ui/styles.py +18 -0
- tunacode/ui/widgets/__init__.py +23 -0
- tunacode/ui/widgets/command_autocomplete.py +62 -0
- tunacode/ui/widgets/editor.py +402 -0
- tunacode/ui/widgets/file_autocomplete.py +47 -0
- tunacode/ui/widgets/messages.py +46 -0
- tunacode/ui/widgets/resource_bar.py +182 -0
- tunacode/ui/widgets/status_bar.py +98 -0
- tunacode/utils/__init__.py +0 -0
- tunacode/utils/config/__init__.py +13 -0
- tunacode/utils/config/user_configuration.py +91 -0
- tunacode/utils/messaging/__init__.py +10 -0
- tunacode/utils/messaging/message_utils.py +34 -0
- tunacode/utils/messaging/token_counter.py +77 -0
- tunacode/utils/parsing/__init__.py +13 -0
- tunacode/utils/parsing/command_parser.py +55 -0
- tunacode/utils/parsing/json_utils.py +188 -0
- tunacode/utils/parsing/retry.py +146 -0
- tunacode/utils/parsing/tool_parser.py +267 -0
- tunacode/utils/security/__init__.py +15 -0
- tunacode/utils/security/command.py +106 -0
- tunacode/utils/system/__init__.py +25 -0
- tunacode/utils/system/gitignore.py +155 -0
- tunacode/utils/system/paths.py +190 -0
- tunacode/utils/ui/__init__.py +9 -0
- tunacode/utils/ui/file_filter.py +135 -0
- tunacode/utils/ui/helpers.py +24 -0
- tunacode_cli-0.1.21.dist-info/METADATA +170 -0
- tunacode_cli-0.1.21.dist-info/RECORD +174 -0
- tunacode_cli-0.1.21.dist-info/WHEEL +4 -0
- tunacode_cli-0.1.21.dist-info/entry_points.txt +2 -0
- tunacode_cli-0.1.21.dist-info/licenses/LICENSE +21 -0
tunacode/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/* TunaCode Textual REPL Stylesheet
|
|
2
|
+
* NeXTSTEP Zone-Based Layout (Modern):
|
|
3
|
+
* ◇ tokens: 1.2k ◇ model ◇ $0.00 ◇ tunacode
|
|
4
|
+
* ┌────────────────────────────────────────────────────┐
|
|
5
|
+
* │ │
|
|
6
|
+
* │ Main viewport (thin subtle border) │
|
|
7
|
+
* │ │
|
|
8
|
+
* ├──────────┬─────────────────────────────────────────┤
|
|
9
|
+
* │ Context │ Editor (Enter to submit) │
|
|
10
|
+
* └──────────┴─────────────────────────────────────────┘
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/* ============================================
|
|
14
|
+
* RESOURCE BAR - Compact, no border
|
|
15
|
+
* NeXTSTEP: "Glanceable, rarely changes"
|
|
16
|
+
* ============================================ */
|
|
17
|
+
ResourceBar {
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 1;
|
|
20
|
+
background: $background;
|
|
21
|
+
padding: 0 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* ============================================
|
|
25
|
+
* PRIMARY VIEWPORT - Thin border, dark bg
|
|
26
|
+
* NeXTSTEP: "Maximum real estate, user focus"
|
|
27
|
+
* ============================================ */
|
|
28
|
+
RichLog {
|
|
29
|
+
height: 1fr;
|
|
30
|
+
background: $background;
|
|
31
|
+
border: solid $border;
|
|
32
|
+
padding: 1;
|
|
33
|
+
scrollbar-gutter: stable;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Streaming - accent border during generation */
|
|
37
|
+
RichLog.streaming {
|
|
38
|
+
border: solid $accent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Pause mode - NeXTSTEP: "Modes must be visually apparent" */
|
|
42
|
+
RichLog.paused {
|
|
43
|
+
border: solid $warning;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ============================================
|
|
47
|
+
* BOTTOM ZONE - Container for tool status + command area
|
|
48
|
+
* Docked as single unit to prevent RichLog (1fr) from crushing children
|
|
49
|
+
* ============================================ */
|
|
50
|
+
/* ============================================
|
|
51
|
+
* UNUSED - Removed complex docking layout
|
|
52
|
+
* ============================================ */
|
|
53
|
+
/*
|
|
54
|
+
#bottom-zone {
|
|
55
|
+
dock: bottom;
|
|
56
|
+
height: auto;
|
|
57
|
+
width: 100%;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#input-row {
|
|
61
|
+
height: 6;
|
|
62
|
+
width: 100%;
|
|
63
|
+
dock: top;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#input-prompt {
|
|
67
|
+
width: 3;
|
|
68
|
+
height: 100%;
|
|
69
|
+
color: $primary;
|
|
70
|
+
background: $background;
|
|
71
|
+
border-top: solid $border;
|
|
72
|
+
border-left: solid $border;
|
|
73
|
+
border-bottom: solid $border;
|
|
74
|
+
padding: 0 0 0 1;
|
|
75
|
+
}
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/* ============================================
|
|
79
|
+
* EDITOR - Input bar (compact)
|
|
80
|
+
* ============================================ */
|
|
81
|
+
Editor {
|
|
82
|
+
width: 1fr;
|
|
83
|
+
height: 6;
|
|
84
|
+
background: $background;
|
|
85
|
+
border-top: solid $border;
|
|
86
|
+
border-right: solid $border;
|
|
87
|
+
border-bottom: solid $border;
|
|
88
|
+
border-left: solid $border;
|
|
89
|
+
padding: 0 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ============================================
|
|
93
|
+
* TEST BAR - Simple test bar to debug text visibility
|
|
94
|
+
* ============================================ */
|
|
95
|
+
TestBar {
|
|
96
|
+
height: 3;
|
|
97
|
+
width: 100%;
|
|
98
|
+
background: $surface;
|
|
99
|
+
border: solid $border;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
TestBar Static {
|
|
103
|
+
color: $text;
|
|
104
|
+
text-style: bold;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#test-left {
|
|
108
|
+
width: 1fr;
|
|
109
|
+
height: 3;
|
|
110
|
+
padding: 1 1;
|
|
111
|
+
text-align: left;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#test-mid {
|
|
115
|
+
width: 1fr;
|
|
116
|
+
height: 3;
|
|
117
|
+
text-align: center;
|
|
118
|
+
border-left: solid $primary;
|
|
119
|
+
border-right: solid $primary;
|
|
120
|
+
padding: 1 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#test-right {
|
|
124
|
+
width: 1fr;
|
|
125
|
+
height: 3;
|
|
126
|
+
text-align: right;
|
|
127
|
+
padding: 1 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ============================================
|
|
131
|
+
* STATUS BAR - 3-column bottom status (no borders)
|
|
132
|
+
* NeXTSTEP: "left=identity, center=metrics, right=state"
|
|
133
|
+
* ============================================ */
|
|
134
|
+
StatusBar {
|
|
135
|
+
height: 1;
|
|
136
|
+
width: 100%;
|
|
137
|
+
background: $background;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
StatusBar Static {
|
|
141
|
+
color: $text;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#status-left {
|
|
145
|
+
width: 1fr;
|
|
146
|
+
text-align: left;
|
|
147
|
+
padding: 0 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#status-mid {
|
|
151
|
+
width: auto;
|
|
152
|
+
text-align: center;
|
|
153
|
+
color: $text-muted;
|
|
154
|
+
padding: 0 2;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#status-right {
|
|
158
|
+
width: 1fr;
|
|
159
|
+
text-align: right;
|
|
160
|
+
padding: 0 1;
|
|
161
|
+
color: $text-muted;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ============================================
|
|
165
|
+
* MODAL OVERLAYS - Tool Confirmation
|
|
166
|
+
* NeXTSTEP: "Controls change appearance immediately"
|
|
167
|
+
* ============================================ */
|
|
168
|
+
ToolConfirmationModal {
|
|
169
|
+
align: center middle;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#modal-body {
|
|
173
|
+
width: 60;
|
|
174
|
+
height: auto;
|
|
175
|
+
border: solid $primary;
|
|
176
|
+
background: $surface;
|
|
177
|
+
padding: 1 2;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#modal-body Label {
|
|
181
|
+
margin-bottom: 1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#modal-body Checkbox {
|
|
185
|
+
margin-top: 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#tool-title {
|
|
189
|
+
text-style: bold;
|
|
190
|
+
color: $primary;
|
|
191
|
+
margin-bottom: 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#actions {
|
|
195
|
+
margin-top: 1;
|
|
196
|
+
align: center middle;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#actions Button {
|
|
200
|
+
margin: 0 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* ============================================
|
|
204
|
+
* RICH PANELS - Tool/Error/Search Display
|
|
205
|
+
* NeXTSTEP: "Objects that look the same should act the same"
|
|
206
|
+
* ============================================ */
|
|
207
|
+
|
|
208
|
+
/* Tool execution panels - cyan accent */
|
|
209
|
+
.tool-panel {
|
|
210
|
+
border: solid $primary;
|
|
211
|
+
background: $surface;
|
|
212
|
+
padding: 0 1;
|
|
213
|
+
margin: 0 0 1 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.tool-panel.running {
|
|
217
|
+
border: solid $accent;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.tool-panel.completed {
|
|
221
|
+
border: solid $success;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.tool-panel.failed {
|
|
225
|
+
border: solid $error;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Error panels - red border, recovery actions */
|
|
229
|
+
.error-panel {
|
|
230
|
+
border: solid $error;
|
|
231
|
+
background: $surface;
|
|
232
|
+
padding: 0 1;
|
|
233
|
+
margin: 0 0 1 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.error-panel.warning {
|
|
237
|
+
border: solid $warning;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.error-panel.info {
|
|
241
|
+
border: solid $secondary;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Search result panels - accent border */
|
|
245
|
+
.search-panel {
|
|
246
|
+
border: solid $accent;
|
|
247
|
+
background: $surface;
|
|
248
|
+
padding: 0 1;
|
|
249
|
+
margin: 0 0 1 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Pagination controls in search */
|
|
253
|
+
.pagination {
|
|
254
|
+
height: 1;
|
|
255
|
+
padding: 0 1;
|
|
256
|
+
color: $text-muted;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* Result item highlighting */
|
|
260
|
+
.result-item {
|
|
261
|
+
padding: 0 1;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.result-item:hover {
|
|
265
|
+
background: $surface;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.result-item.selected {
|
|
269
|
+
background: $primary 20%;
|
|
270
|
+
border-left: solid $primary;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Recovery command styling */
|
|
274
|
+
.recovery-command {
|
|
275
|
+
color: $primary;
|
|
276
|
+
text-style: underline;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Context detail styling */
|
|
280
|
+
.context-detail {
|
|
281
|
+
color: $text-muted;
|
|
282
|
+
padding-left: 2;
|
|
283
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Config package
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: tunacode.configuration.defaults
|
|
3
|
+
|
|
4
|
+
Default configuration values for the TunaCode CLI.
|
|
5
|
+
Provides sensible defaults for user configuration and environment variables.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from tunacode.constants import ENV_OPENAI_BASE_URL, GUIDE_FILE_NAME
|
|
9
|
+
from tunacode.types import UserConfig
|
|
10
|
+
|
|
11
|
+
DEFAULT_USER_CONFIG: UserConfig = {
|
|
12
|
+
"default_model": "openrouter:openai/gpt-4.1",
|
|
13
|
+
"env": {
|
|
14
|
+
"ANTHROPIC_API_KEY": "",
|
|
15
|
+
"GEMINI_API_KEY": "",
|
|
16
|
+
"OPENAI_API_KEY": "",
|
|
17
|
+
ENV_OPENAI_BASE_URL: "",
|
|
18
|
+
"OPENROUTER_API_KEY": "",
|
|
19
|
+
},
|
|
20
|
+
"settings": {
|
|
21
|
+
"max_retries": 3,
|
|
22
|
+
"max_iterations": 40,
|
|
23
|
+
"request_delay": 0.0,
|
|
24
|
+
"global_request_timeout": 90.0,
|
|
25
|
+
"tool_ignore": [],
|
|
26
|
+
"guide_file": GUIDE_FILE_NAME,
|
|
27
|
+
"fallback_response": True,
|
|
28
|
+
"fallback_verbosity": "normal", # Options: minimal, normal, detailed
|
|
29
|
+
"context_window_size": 200000,
|
|
30
|
+
"enable_streaming": True, # Always enable streaming
|
|
31
|
+
"theme": "dracula", # UI theme name
|
|
32
|
+
"ripgrep": {
|
|
33
|
+
"timeout": 10, # Search timeout in seconds
|
|
34
|
+
"max_buffer_size": 1048576, # 1MB max output buffer
|
|
35
|
+
"max_results": 100, # Maximum results per search
|
|
36
|
+
"enable_metrics": False, # Enable performance metrics collection
|
|
37
|
+
"debug": False, # Enable debug logging for ripgrep operations
|
|
38
|
+
},
|
|
39
|
+
"lsp": {
|
|
40
|
+
"enabled": True, # Requires pyright: uv pip install pyright
|
|
41
|
+
"timeout": 5.0, # Maximum seconds to wait for diagnostics
|
|
42
|
+
"max_diagnostics": 20, # Maximum number of diagnostics to show
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: tunacode.configuration.models
|
|
3
|
+
|
|
4
|
+
Configuration for loading model data from models_registry.json.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from tunacode.constants import DEFAULT_CONTEXT_WINDOW
|
|
8
|
+
|
|
9
|
+
# --- Models.dev Registry Functions ---
|
|
10
|
+
|
|
11
|
+
MODELS_REGISTRY_FILE_NAME = "models_registry.json"
|
|
12
|
+
|
|
13
|
+
_models_registry_cache: dict | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_model_string(model_string: str) -> tuple[str, str]:
|
|
17
|
+
"""Parse 'provider:model_id' into (provider_id, model_id).
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
model_string: Full model identifier (e.g., "openrouter:openai/gpt-4.1")
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Tuple of (provider_id, model_id)
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: If model_string doesn't contain a colon separator
|
|
27
|
+
"""
|
|
28
|
+
if ":" not in model_string:
|
|
29
|
+
raise ValueError(f"Invalid model string format: {model_string}")
|
|
30
|
+
parts = model_string.split(":", 1)
|
|
31
|
+
return (parts[0], parts[1])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_models_registry() -> dict:
|
|
35
|
+
"""Load bundled models.dev registry from JSON file.
|
|
36
|
+
|
|
37
|
+
Returns cached data on subsequent calls for performance.
|
|
38
|
+
"""
|
|
39
|
+
global _models_registry_cache
|
|
40
|
+
if _models_registry_cache is not None:
|
|
41
|
+
return _models_registry_cache
|
|
42
|
+
|
|
43
|
+
import json
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
|
|
46
|
+
registry_path = Path(__file__).parent / MODELS_REGISTRY_FILE_NAME
|
|
47
|
+
with open(registry_path) as f:
|
|
48
|
+
_models_registry_cache = json.load(f)
|
|
49
|
+
return _models_registry_cache
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_cached_models_registry() -> dict | None:
|
|
53
|
+
"""Return cached registry data if already loaded."""
|
|
54
|
+
return _models_registry_cache
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_providers() -> list[tuple[str, str]]:
|
|
58
|
+
"""Return (display_name, id) tuples for all providers.
|
|
59
|
+
|
|
60
|
+
Sorted alphabetically by display name.
|
|
61
|
+
"""
|
|
62
|
+
registry = load_models_registry()
|
|
63
|
+
providers = [(p["name"], p["id"]) for p in registry.values()]
|
|
64
|
+
return sorted(providers, key=lambda x: x[0].lower())
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_models_for_provider(provider_id: str) -> list[tuple[str, str]]:
|
|
68
|
+
"""Return (display_name, id) tuples for a provider's models.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
provider_id: The provider identifier (e.g., "openai", "anthropic")
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List of (model_name, model_id) tuples, sorted alphabetically.
|
|
75
|
+
"""
|
|
76
|
+
registry = load_models_registry()
|
|
77
|
+
provider = registry.get(provider_id, {})
|
|
78
|
+
models = provider.get("models", {})
|
|
79
|
+
result = [(m["name"], mid) for mid, m in models.items()]
|
|
80
|
+
return sorted(result, key=lambda x: x[0].lower())
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_provider_env_var(provider_id: str) -> str:
|
|
84
|
+
"""Return the environment variable name for a provider's API key.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
provider_id: The provider identifier
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Environment variable name (e.g., "OPENAI_API_KEY")
|
|
91
|
+
"""
|
|
92
|
+
registry = get_cached_models_registry()
|
|
93
|
+
if registry is None:
|
|
94
|
+
return f"{provider_id.upper()}_API_KEY"
|
|
95
|
+
|
|
96
|
+
provider = registry.get(provider_id, {})
|
|
97
|
+
env_vars = provider.get("env", [])
|
|
98
|
+
if env_vars:
|
|
99
|
+
return env_vars[0]
|
|
100
|
+
return f"{provider_id.upper()}_API_KEY"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_provider_base_url(provider_id: str) -> str | None:
|
|
104
|
+
"""Return the API base URL for a provider.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
provider_id: The provider identifier
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Base URL string or None if not specified
|
|
111
|
+
"""
|
|
112
|
+
registry = get_cached_models_registry()
|
|
113
|
+
if registry is None:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
provider = registry.get(provider_id, {})
|
|
117
|
+
return provider.get("api")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_model_context_window(model_string: str) -> int:
|
|
121
|
+
"""Get context window limit for a model from cached models_registry data.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
model_string: Full model identifier (e.g., "openrouter:openai/gpt-4.1")
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Context window size in tokens. Falls back to DEFAULT_CONTEXT_WINDOW
|
|
128
|
+
if registry is not loaded, model not found, or limit not specified.
|
|
129
|
+
"""
|
|
130
|
+
registry = get_cached_models_registry()
|
|
131
|
+
if registry is None:
|
|
132
|
+
return DEFAULT_CONTEXT_WINDOW
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
provider_id, model_id = parse_model_string(model_string)
|
|
136
|
+
except ValueError:
|
|
137
|
+
return DEFAULT_CONTEXT_WINDOW
|
|
138
|
+
|
|
139
|
+
provider = registry.get(provider_id, {})
|
|
140
|
+
model = provider.get("models", {}).get(model_id, {})
|
|
141
|
+
limit = model.get("limit", {})
|
|
142
|
+
|
|
143
|
+
context = limit.get("context")
|
|
144
|
+
if context is None:
|
|
145
|
+
return DEFAULT_CONTEXT_WINDOW
|
|
146
|
+
|
|
147
|
+
return context
|