bingocode 1.1.200 → 1.1.201
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/package.json
CHANGED
|
@@ -1,229 +1,229 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
|
|
3
|
-
# Provider Preset Configuration
|
|
4
|
-
# fields array declares fields to be filled when adding
|
|
5
|
-
# key: 'name' | 'apiKey' | 'baseUrl' mapped directly to top-level fields, others saved in extra.<key>
|
|
6
|
-
# secret: true uses mask display in frontend
|
|
7
|
-
#
|
|
8
|
-
# modelsUrl: model list path relative to baseUrl, empty string means dynamic pulling not supported
|
|
9
|
-
# modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
|
|
10
|
-
# x-api-key → x-api-key: <apiKey> + anthropic-version header
|
|
11
|
-
# modelsDataPath: record field name for models array in JSON response (almost always 'data')
|
|
12
|
-
|
|
13
|
-
presets:
|
|
14
|
-
- id: official
|
|
15
|
-
name: Claude Official
|
|
16
|
-
baseUrl: ''
|
|
17
|
-
apiFormat: anthropic
|
|
18
|
-
needsApiKey: false
|
|
19
|
-
websiteUrl: https://www.anthropic.com/claude-code
|
|
20
|
-
modelsUrl: /v1/models
|
|
21
|
-
modelsAuthStyle: x-api-key
|
|
22
|
-
modelsDataPath: data
|
|
23
|
-
fields:
|
|
24
|
-
- key: name
|
|
25
|
-
label: Provider Nickname
|
|
26
|
-
required: true
|
|
27
|
-
secret: false
|
|
28
|
-
placeholder: 'e.g. Claude Official'
|
|
29
|
-
|
|
30
|
-
- id: openai
|
|
31
|
-
name: OpenAI
|
|
32
|
-
baseUrl: https://api.openai.com/v1
|
|
33
|
-
apiFormat: openai_chat
|
|
34
|
-
needsApiKey: true
|
|
35
|
-
websiteUrl: https://platform.openai.com
|
|
36
|
-
modelsUrl: /v1/models
|
|
37
|
-
modelsAuthStyle: bearer
|
|
38
|
-
modelsDataPath: data
|
|
39
|
-
fields:
|
|
40
|
-
- key: name
|
|
41
|
-
label: Provider Nickname
|
|
42
|
-
required: true
|
|
43
|
-
secret: false
|
|
44
|
-
placeholder: 'e.g. My OpenAI'
|
|
45
|
-
- key: apiKey
|
|
46
|
-
label: API Key
|
|
47
|
-
required: true
|
|
48
|
-
secret: true
|
|
49
|
-
placeholder: 'sk-...'
|
|
50
|
-
- key: baseUrl
|
|
51
|
-
label: Base URL (Optional)
|
|
52
|
-
required: false
|
|
53
|
-
secret: false
|
|
54
|
-
default: https://api.openai.com/v1
|
|
55
|
-
placeholder: 'https://api.openai.com/v1'
|
|
56
|
-
|
|
57
|
-
- id: gemini
|
|
58
|
-
name: Google Gemini
|
|
59
|
-
baseUrl: https://generativelanguage.googleapis.com/v1beta/openai
|
|
60
|
-
apiFormat: openai_chat
|
|
61
|
-
needsApiKey: true
|
|
62
|
-
websiteUrl: https://aistudio.google.com
|
|
63
|
-
modelsUrl: /v1/models
|
|
64
|
-
modelsAuthStyle: bearer
|
|
65
|
-
modelsDataPath: data
|
|
66
|
-
fields:
|
|
67
|
-
- key: name
|
|
68
|
-
label: Provider Nickname
|
|
69
|
-
required: true
|
|
70
|
-
secret: false
|
|
71
|
-
placeholder: 'e.g. My Gemini'
|
|
72
|
-
- key: apiKey
|
|
73
|
-
label: API Key
|
|
74
|
-
required: true
|
|
75
|
-
secret: true
|
|
76
|
-
placeholder: 'Gemini API Key'
|
|
77
|
-
|
|
78
|
-
- id: mistral
|
|
79
|
-
name: Mistral AI
|
|
80
|
-
baseUrl: https://api.mistral.ai/v1
|
|
81
|
-
apiFormat: openai_chat
|
|
82
|
-
needsApiKey: true
|
|
83
|
-
websiteUrl: https://console.mistral.ai
|
|
84
|
-
modelsUrl: /v1/models
|
|
85
|
-
modelsAuthStyle: bearer
|
|
86
|
-
modelsDataPath: data
|
|
87
|
-
fields:
|
|
88
|
-
- key: name
|
|
89
|
-
label: Provider Nickname
|
|
90
|
-
required: true
|
|
91
|
-
secret: false
|
|
92
|
-
placeholder: 'e.g. My Mistral'
|
|
93
|
-
- key: apiKey
|
|
94
|
-
label: API Key
|
|
95
|
-
required: true
|
|
96
|
-
secret: true
|
|
97
|
-
placeholder: 'Mistral API Key'
|
|
98
|
-
|
|
99
|
-
- id: deepseek
|
|
100
|
-
name: DeepSeek
|
|
101
|
-
baseUrl: https://api.deepseek.com/anthropic
|
|
102
|
-
apiFormat: anthropic
|
|
103
|
-
needsApiKey: true
|
|
104
|
-
websiteUrl: https://platform.deepseek.com
|
|
105
|
-
# Models list follows OpenAI endpoint (no /v1/models under /anthropic)
|
|
106
|
-
modelsUrl: https://api.deepseek.com/v1/models
|
|
107
|
-
modelsAuthStyle: bearer
|
|
108
|
-
modelsDataPath: data
|
|
109
|
-
fields:
|
|
110
|
-
- key: name
|
|
111
|
-
label: Provider Nickname
|
|
112
|
-
required: true
|
|
113
|
-
secret: false
|
|
114
|
-
placeholder: 'e.g. My DeepSeek'
|
|
115
|
-
- key: apiKey
|
|
116
|
-
label: API Key
|
|
117
|
-
required: true
|
|
118
|
-
secret: true
|
|
119
|
-
placeholder: 'sk-...'
|
|
120
|
-
|
|
121
|
-
- id: nvidia
|
|
122
|
-
name: NVIDIA API
|
|
123
|
-
baseUrl: https://integrate.api.nvidia.com/v1
|
|
124
|
-
apiFormat: openai_chat
|
|
125
|
-
needsApiKey: true
|
|
126
|
-
websiteUrl: https://build.nvidia.com/explore/discover
|
|
127
|
-
modelsUrl: /models
|
|
128
|
-
modelsAuthStyle: bearer
|
|
129
|
-
modelsDataPath: data
|
|
130
|
-
fields:
|
|
131
|
-
- key: name
|
|
132
|
-
label: Provider Nickname
|
|
133
|
-
required: true
|
|
134
|
-
secret: false
|
|
135
|
-
placeholder: 'e.g. My NVIDIA'
|
|
136
|
-
- key: apiKey
|
|
137
|
-
label: API Key
|
|
138
|
-
required: true
|
|
139
|
-
secret: true
|
|
140
|
-
placeholder: 'nvapi-...'
|
|
141
|
-
|
|
142
|
-
- id: zhipuglm
|
|
143
|
-
name: Zhipu GLM
|
|
144
|
-
baseUrl: https://open.bigmodel.cn/api/paas/v4
|
|
145
|
-
apiFormat: openai_chat
|
|
146
|
-
needsApiKey: true
|
|
147
|
-
websiteUrl: https://open.bigmodel.cn
|
|
148
|
-
modelsUrl: /v4/models
|
|
149
|
-
modelsAuthStyle: bearer
|
|
150
|
-
modelsDataPath: data
|
|
151
|
-
fields:
|
|
152
|
-
- key: name
|
|
153
|
-
label: Provider Nickname
|
|
154
|
-
required: true
|
|
155
|
-
secret: false
|
|
156
|
-
placeholder: 'e.g. My GLM'
|
|
157
|
-
- key: apiKey
|
|
158
|
-
label: API Key
|
|
159
|
-
required: true
|
|
160
|
-
secret: true
|
|
161
|
-
placeholder: 'Zhipu API Key (glm-5.1)'
|
|
162
|
-
|
|
163
|
-
- id: kimi
|
|
164
|
-
name: Kimi
|
|
165
|
-
baseUrl: https://api.moonshot.ai/v1
|
|
166
|
-
apiFormat: openai_chat
|
|
167
|
-
needsApiKey: true
|
|
168
|
-
websiteUrl: https://platform.moonshot.cn
|
|
169
|
-
modelsUrl: /models
|
|
170
|
-
modelsAuthStyle: bearer
|
|
171
|
-
modelsDataPath: data
|
|
172
|
-
fields:
|
|
173
|
-
- key: name
|
|
174
|
-
label: Provider Nickname
|
|
175
|
-
required: true
|
|
176
|
-
secret: false
|
|
177
|
-
placeholder: 'e.g. My Kimi'
|
|
178
|
-
- key: apiKey
|
|
179
|
-
label: API Key
|
|
180
|
-
required: true
|
|
181
|
-
secret: true
|
|
182
|
-
placeholder: 'Moonshot API Key (kimi-k2.6)'
|
|
183
|
-
|
|
184
|
-
- id: minimax
|
|
185
|
-
name: MiniMax
|
|
186
|
-
baseUrl: https://api.minimax.io/v1
|
|
187
|
-
apiFormat: openai_chat
|
|
188
|
-
needsApiKey: true
|
|
189
|
-
websiteUrl: https://platform.minimaxi.com
|
|
190
|
-
modelsUrl: /models
|
|
191
|
-
modelsAuthStyle: bearer
|
|
192
|
-
modelsDataPath: data
|
|
193
|
-
fields:
|
|
194
|
-
- key: name
|
|
195
|
-
label: Provider Nickname
|
|
196
|
-
required: true
|
|
197
|
-
secret: false
|
|
198
|
-
placeholder: 'e.g. My MiniMax'
|
|
199
|
-
- key: apiKey
|
|
200
|
-
label: API Key
|
|
201
|
-
required: true
|
|
202
|
-
secret: true
|
|
203
|
-
placeholder: 'MiniMax API Key (MiniMax-M2.7)'
|
|
204
|
-
|
|
205
|
-
- id: custom
|
|
206
|
-
name: Custom
|
|
207
|
-
baseUrl: ''
|
|
208
|
-
apiFormat: openai_chat
|
|
209
|
-
needsApiKey: true
|
|
210
|
-
websiteUrl: ''
|
|
211
|
-
modelsUrl: /v1/models
|
|
212
|
-
modelsAuthStyle: bearer
|
|
213
|
-
modelsDataPath: data
|
|
214
|
-
fields:
|
|
215
|
-
- key: name
|
|
216
|
-
label: Provider Nickname
|
|
217
|
-
required: true
|
|
218
|
-
secret: false
|
|
219
|
-
placeholder: 'e.g. My Custom Provider'
|
|
220
|
-
- key: baseUrl
|
|
221
|
-
label: Base URL
|
|
222
|
-
required: true
|
|
223
|
-
secret: false
|
|
224
|
-
placeholder: 'https://your-api-endpoint.com/v1'
|
|
225
|
-
- key: apiKey
|
|
226
|
-
label: API Key
|
|
227
|
-
required: false
|
|
228
|
-
secret: true
|
|
229
|
-
placeholder: '(Optional) API Key'
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
# Provider Preset Configuration
|
|
4
|
+
# fields array declares fields to be filled when adding
|
|
5
|
+
# key: 'name' | 'apiKey' | 'baseUrl' mapped directly to top-level fields, others saved in extra.<key>
|
|
6
|
+
# secret: true uses mask display in frontend
|
|
7
|
+
#
|
|
8
|
+
# modelsUrl: model list path relative to baseUrl, empty string means dynamic pulling not supported
|
|
9
|
+
# modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
|
|
10
|
+
# x-api-key → x-api-key: <apiKey> + anthropic-version header
|
|
11
|
+
# modelsDataPath: record field name for models array in JSON response (almost always 'data')
|
|
12
|
+
|
|
13
|
+
presets:
|
|
14
|
+
- id: official
|
|
15
|
+
name: Claude Official
|
|
16
|
+
baseUrl: ''
|
|
17
|
+
apiFormat: anthropic
|
|
18
|
+
needsApiKey: false
|
|
19
|
+
websiteUrl: https://www.anthropic.com/claude-code
|
|
20
|
+
modelsUrl: /v1/models
|
|
21
|
+
modelsAuthStyle: x-api-key
|
|
22
|
+
modelsDataPath: data
|
|
23
|
+
fields:
|
|
24
|
+
- key: name
|
|
25
|
+
label: Provider Nickname
|
|
26
|
+
required: true
|
|
27
|
+
secret: false
|
|
28
|
+
placeholder: 'e.g. Claude Official'
|
|
29
|
+
|
|
30
|
+
- id: openai
|
|
31
|
+
name: OpenAI
|
|
32
|
+
baseUrl: https://api.openai.com/v1
|
|
33
|
+
apiFormat: openai_chat
|
|
34
|
+
needsApiKey: true
|
|
35
|
+
websiteUrl: https://platform.openai.com
|
|
36
|
+
modelsUrl: /v1/models
|
|
37
|
+
modelsAuthStyle: bearer
|
|
38
|
+
modelsDataPath: data
|
|
39
|
+
fields:
|
|
40
|
+
- key: name
|
|
41
|
+
label: Provider Nickname
|
|
42
|
+
required: true
|
|
43
|
+
secret: false
|
|
44
|
+
placeholder: 'e.g. My OpenAI'
|
|
45
|
+
- key: apiKey
|
|
46
|
+
label: API Key
|
|
47
|
+
required: true
|
|
48
|
+
secret: true
|
|
49
|
+
placeholder: 'sk-...'
|
|
50
|
+
- key: baseUrl
|
|
51
|
+
label: Base URL (Optional)
|
|
52
|
+
required: false
|
|
53
|
+
secret: false
|
|
54
|
+
default: https://api.openai.com/v1
|
|
55
|
+
placeholder: 'https://api.openai.com/v1'
|
|
56
|
+
|
|
57
|
+
- id: gemini
|
|
58
|
+
name: Google Gemini
|
|
59
|
+
baseUrl: https://generativelanguage.googleapis.com/v1beta/openai
|
|
60
|
+
apiFormat: openai_chat
|
|
61
|
+
needsApiKey: true
|
|
62
|
+
websiteUrl: https://aistudio.google.com
|
|
63
|
+
modelsUrl: /v1/models
|
|
64
|
+
modelsAuthStyle: bearer
|
|
65
|
+
modelsDataPath: data
|
|
66
|
+
fields:
|
|
67
|
+
- key: name
|
|
68
|
+
label: Provider Nickname
|
|
69
|
+
required: true
|
|
70
|
+
secret: false
|
|
71
|
+
placeholder: 'e.g. My Gemini'
|
|
72
|
+
- key: apiKey
|
|
73
|
+
label: API Key
|
|
74
|
+
required: true
|
|
75
|
+
secret: true
|
|
76
|
+
placeholder: 'Gemini API Key'
|
|
77
|
+
|
|
78
|
+
- id: mistral
|
|
79
|
+
name: Mistral AI
|
|
80
|
+
baseUrl: https://api.mistral.ai/v1
|
|
81
|
+
apiFormat: openai_chat
|
|
82
|
+
needsApiKey: true
|
|
83
|
+
websiteUrl: https://console.mistral.ai
|
|
84
|
+
modelsUrl: /v1/models
|
|
85
|
+
modelsAuthStyle: bearer
|
|
86
|
+
modelsDataPath: data
|
|
87
|
+
fields:
|
|
88
|
+
- key: name
|
|
89
|
+
label: Provider Nickname
|
|
90
|
+
required: true
|
|
91
|
+
secret: false
|
|
92
|
+
placeholder: 'e.g. My Mistral'
|
|
93
|
+
- key: apiKey
|
|
94
|
+
label: API Key
|
|
95
|
+
required: true
|
|
96
|
+
secret: true
|
|
97
|
+
placeholder: 'Mistral API Key'
|
|
98
|
+
|
|
99
|
+
- id: deepseek
|
|
100
|
+
name: DeepSeek
|
|
101
|
+
baseUrl: https://api.deepseek.com/anthropic
|
|
102
|
+
apiFormat: anthropic
|
|
103
|
+
needsApiKey: true
|
|
104
|
+
websiteUrl: https://platform.deepseek.com
|
|
105
|
+
# Models list follows OpenAI endpoint (no /v1/models under /anthropic)
|
|
106
|
+
modelsUrl: https://api.deepseek.com/v1/models
|
|
107
|
+
modelsAuthStyle: bearer
|
|
108
|
+
modelsDataPath: data
|
|
109
|
+
fields:
|
|
110
|
+
- key: name
|
|
111
|
+
label: Provider Nickname
|
|
112
|
+
required: true
|
|
113
|
+
secret: false
|
|
114
|
+
placeholder: 'e.g. My DeepSeek'
|
|
115
|
+
- key: apiKey
|
|
116
|
+
label: API Key
|
|
117
|
+
required: true
|
|
118
|
+
secret: true
|
|
119
|
+
placeholder: 'sk-...'
|
|
120
|
+
|
|
121
|
+
- id: nvidia
|
|
122
|
+
name: NVIDIA API
|
|
123
|
+
baseUrl: https://integrate.api.nvidia.com/v1
|
|
124
|
+
apiFormat: openai_chat
|
|
125
|
+
needsApiKey: true
|
|
126
|
+
websiteUrl: https://build.nvidia.com/explore/discover
|
|
127
|
+
modelsUrl: /models
|
|
128
|
+
modelsAuthStyle: bearer
|
|
129
|
+
modelsDataPath: data
|
|
130
|
+
fields:
|
|
131
|
+
- key: name
|
|
132
|
+
label: Provider Nickname
|
|
133
|
+
required: true
|
|
134
|
+
secret: false
|
|
135
|
+
placeholder: 'e.g. My NVIDIA'
|
|
136
|
+
- key: apiKey
|
|
137
|
+
label: API Key
|
|
138
|
+
required: true
|
|
139
|
+
secret: true
|
|
140
|
+
placeholder: 'nvapi-...'
|
|
141
|
+
|
|
142
|
+
- id: zhipuglm
|
|
143
|
+
name: Zhipu GLM
|
|
144
|
+
baseUrl: https://open.bigmodel.cn/api/paas/v4
|
|
145
|
+
apiFormat: openai_chat
|
|
146
|
+
needsApiKey: true
|
|
147
|
+
websiteUrl: https://open.bigmodel.cn
|
|
148
|
+
modelsUrl: /v4/models
|
|
149
|
+
modelsAuthStyle: bearer
|
|
150
|
+
modelsDataPath: data
|
|
151
|
+
fields:
|
|
152
|
+
- key: name
|
|
153
|
+
label: Provider Nickname
|
|
154
|
+
required: true
|
|
155
|
+
secret: false
|
|
156
|
+
placeholder: 'e.g. My GLM'
|
|
157
|
+
- key: apiKey
|
|
158
|
+
label: API Key
|
|
159
|
+
required: true
|
|
160
|
+
secret: true
|
|
161
|
+
placeholder: 'Zhipu API Key (glm-5.1)'
|
|
162
|
+
|
|
163
|
+
- id: kimi
|
|
164
|
+
name: Kimi
|
|
165
|
+
baseUrl: https://api.moonshot.ai/v1
|
|
166
|
+
apiFormat: openai_chat
|
|
167
|
+
needsApiKey: true
|
|
168
|
+
websiteUrl: https://platform.moonshot.cn
|
|
169
|
+
modelsUrl: /models
|
|
170
|
+
modelsAuthStyle: bearer
|
|
171
|
+
modelsDataPath: data
|
|
172
|
+
fields:
|
|
173
|
+
- key: name
|
|
174
|
+
label: Provider Nickname
|
|
175
|
+
required: true
|
|
176
|
+
secret: false
|
|
177
|
+
placeholder: 'e.g. My Kimi'
|
|
178
|
+
- key: apiKey
|
|
179
|
+
label: API Key
|
|
180
|
+
required: true
|
|
181
|
+
secret: true
|
|
182
|
+
placeholder: 'Moonshot API Key (kimi-k2.6)'
|
|
183
|
+
|
|
184
|
+
- id: minimax
|
|
185
|
+
name: MiniMax
|
|
186
|
+
baseUrl: https://api.minimax.io/v1
|
|
187
|
+
apiFormat: openai_chat
|
|
188
|
+
needsApiKey: true
|
|
189
|
+
websiteUrl: https://platform.minimaxi.com
|
|
190
|
+
modelsUrl: /models
|
|
191
|
+
modelsAuthStyle: bearer
|
|
192
|
+
modelsDataPath: data
|
|
193
|
+
fields:
|
|
194
|
+
- key: name
|
|
195
|
+
label: Provider Nickname
|
|
196
|
+
required: true
|
|
197
|
+
secret: false
|
|
198
|
+
placeholder: 'e.g. My MiniMax'
|
|
199
|
+
- key: apiKey
|
|
200
|
+
label: API Key
|
|
201
|
+
required: true
|
|
202
|
+
secret: true
|
|
203
|
+
placeholder: 'MiniMax API Key (MiniMax-M2.7)'
|
|
204
|
+
|
|
205
|
+
- id: custom
|
|
206
|
+
name: Custom
|
|
207
|
+
baseUrl: ''
|
|
208
|
+
apiFormat: openai_chat
|
|
209
|
+
needsApiKey: true
|
|
210
|
+
websiteUrl: ''
|
|
211
|
+
modelsUrl: /v1/models
|
|
212
|
+
modelsAuthStyle: bearer
|
|
213
|
+
modelsDataPath: data
|
|
214
|
+
fields:
|
|
215
|
+
- key: name
|
|
216
|
+
label: Provider Nickname
|
|
217
|
+
required: true
|
|
218
|
+
secret: false
|
|
219
|
+
placeholder: 'e.g. My Custom Provider'
|
|
220
|
+
- key: baseUrl
|
|
221
|
+
label: Base URL
|
|
222
|
+
required: true
|
|
223
|
+
secret: false
|
|
224
|
+
placeholder: 'https://your-api-endpoint.com/v1'
|
|
225
|
+
- key: apiKey
|
|
226
|
+
label: API Key
|
|
227
|
+
required: false
|
|
228
|
+
secret: true
|
|
229
|
+
placeholder: '(Optional) API Key'
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import * as fs from 'fs/promises'
|
|
10
10
|
import * as path from 'path'
|
|
11
11
|
import * as os from 'os'
|
|
12
|
+
import { parse as parseJsonc } from 'jsonc-parser/lib/esm/main.js'
|
|
12
13
|
import { getDirectFetchOptions } from '../../utils/proxy.ts'
|
|
13
14
|
import { ApiError } from '../middleware/errorHandler.js'
|
|
14
15
|
import { anthropicToOpenaiChat } from '../proxy/transform/anthropicToOpenaiChat.js'
|
|
@@ -268,6 +269,18 @@ export class ProviderService {
|
|
|
268
269
|
// --- Settings sync ---
|
|
269
270
|
|
|
270
271
|
private async syncToSettings(provider: SavedProvider): Promise<void> {
|
|
272
|
+
// Prefer slot-based model IDs when slots are configured, so that the
|
|
273
|
+
// slot table (set via "Configure Model Slots" in the TUI) is the single
|
|
274
|
+
// source of truth. Falling back to provider.models only when no slots
|
|
275
|
+
// exist keeps things working for users who haven't configured slots yet.
|
|
276
|
+
const slots = await this.readSlots()
|
|
277
|
+
const hasSlots = Object.keys(slots).length > 0
|
|
278
|
+
if (hasSlots) {
|
|
279
|
+
await this.syncSettingsForSlots(slots)
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Legacy fallback: no slots configured — use provider.models directly
|
|
271
284
|
const settings = await this.readSettings()
|
|
272
285
|
const existingEnv = (settings.env as Record<string, string>) || {}
|
|
273
286
|
|
|
@@ -620,7 +633,9 @@ export class ProviderService {
|
|
|
620
633
|
let originalContent: string | null = null
|
|
621
634
|
try {
|
|
622
635
|
originalContent = await fs.readFile(settingsPath, "utf-8")
|
|
623
|
-
settings
|
|
636
|
+
// VS Code settings.json uses JSONC (JSON with comments) —
|
|
637
|
+
// parseJsonc handles comments/trailing commas, unlike JSON.parse
|
|
638
|
+
settings = parseJsonc(originalContent) as Record<string, unknown>
|
|
624
639
|
} catch {
|
|
625
640
|
// File does not exist yet — start fresh
|
|
626
641
|
}
|
|
@@ -650,7 +665,9 @@ export class ProviderService {
|
|
|
650
665
|
|
|
651
666
|
try {
|
|
652
667
|
await fs.mkdir(path.dirname(settingsPath), { recursive: true })
|
|
653
|
-
|
|
668
|
+
const tmpPath = `${settingsPath}.tmp.${Date.now()}`
|
|
669
|
+
await fs.writeFile(tmpPath, JSON.stringify(settings, null, 4) + '\n')
|
|
670
|
+
await fs.rename(tmpPath, settingsPath)
|
|
654
671
|
writtenPaths.push(settingsPath)
|
|
655
672
|
} catch (err) {
|
|
656
673
|
failedPaths.push(settingsPath)
|
|
@@ -661,7 +678,9 @@ export class ProviderService {
|
|
|
661
678
|
// Fix #11: Attempt restore from backup on write failure
|
|
662
679
|
if (originalContent !== null) {
|
|
663
680
|
try {
|
|
664
|
-
|
|
681
|
+
const tmpPath2 = `${settingsPath}.tmp.${Date.now()}`
|
|
682
|
+
await fs.writeFile(tmpPath2, originalContent, "utf-8")
|
|
683
|
+
await fs.rename(tmpPath2, settingsPath)
|
|
665
684
|
console.error(`[ProviderService] Restored ${settingsPath} from in-memory copy after write failure`)
|
|
666
685
|
} catch {
|
|
667
686
|
// In-memory restore failed, try .bak file
|
|
@@ -694,7 +713,9 @@ export class ProviderService {
|
|
|
694
713
|
const original = originalContents.get(p)
|
|
695
714
|
try {
|
|
696
715
|
if (original !== null) {
|
|
697
|
-
|
|
716
|
+
const tmpPath = `${p}.tmp.${Date.now()}`
|
|
717
|
+
await fs.writeFile(tmpPath, original, "utf-8")
|
|
718
|
+
await fs.rename(tmpPath, p)
|
|
698
719
|
} else {
|
|
699
720
|
await fs.unlink(p).catch(() => {})
|
|
700
721
|
}
|
|
@@ -754,14 +775,16 @@ export class ProviderService {
|
|
|
754
775
|
for (const settingsPath of linkedPaths) {
|
|
755
776
|
try {
|
|
756
777
|
const raw = await fs.readFile(settingsPath, "utf-8")
|
|
757
|
-
const settings =
|
|
778
|
+
const settings = parseJsonc(raw) as Record<string, unknown>
|
|
758
779
|
settings.claudeCode = preLinkClaudeCode[settingsPath] ?? {
|
|
759
780
|
preferredLocation: "panel",
|
|
760
781
|
environmentVariables: [
|
|
761
782
|
{ name: "ANTHROPIC_BASE_URL", value: "https://api.anthropic.com" },
|
|
762
783
|
],
|
|
763
784
|
}
|
|
764
|
-
|
|
785
|
+
const tmpPath = `${settingsPath}.tmp.${Date.now()}`
|
|
786
|
+
await fs.writeFile(tmpPath, JSON.stringify(settings, null, 4) + '\n')
|
|
787
|
+
await fs.rename(tmpPath, settingsPath)
|
|
765
788
|
} catch {
|
|
766
789
|
// File may have been moved or deleted since linking — harmless
|
|
767
790
|
}
|
|
@@ -5,7 +5,7 @@ import { ConfigurableShortcutHint } from 'src/components/ConfigurableShortcutHin
|
|
|
5
5
|
import { CtrlOToExpand, SubAgentProvider } from 'src/components/CtrlOToExpand.js';
|
|
6
6
|
import { Byline } from 'src/components/design-system/Byline.js';
|
|
7
7
|
import { KeyboardShortcutHint } from 'src/components/design-system/KeyboardShortcutHint.js';
|
|
8
|
-
import
|
|
8
|
+
import { z } from 'zod/v4';
|
|
9
9
|
import { AgentProgressLine } from '../../components/AgentProgressLine.js';
|
|
10
10
|
import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js';
|
|
11
11
|
import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage.js';
|
|
@@ -15,14 +15,14 @@ import { MessageResponse } from '../../components/MessageResponse.js';
|
|
|
15
15
|
import { ToolUseLoader } from '../../components/ToolUseLoader.js';
|
|
16
16
|
import { Box, Text } from '../../ink.js';
|
|
17
17
|
import { getDumpPromptsPath } from '../../services/api/dumpPrompts.js';
|
|
18
|
-
import { findToolByName, type Tools } from '../../Tool.js';
|
|
18
|
+
import { buildTool, findToolByName, toolMatchesName, type Tool, type Tools } from '../../Tool.js';
|
|
19
19
|
import type { Message, ProgressMessage } from '../../types/message.js';
|
|
20
20
|
import type { AgentToolProgress } from '../../types/tools.js';
|
|
21
21
|
import { count } from '../../utils/array.js';
|
|
22
22
|
import { getSearchOrReadFromContent, getSearchReadSummaryText } from '../../utils/collapseReadSearch.js';
|
|
23
23
|
import { getDisplayPath } from '../../utils/file.js';
|
|
24
24
|
import { formatDuration, formatNumber } from '../../utils/format.js';
|
|
25
|
-
import { buildSubagentLookups, createAssistantMessage, EMPTY_LOOKUPS } from '../../utils/messages.js';
|
|
25
|
+
import { buildSubagentLookups, createAssistantMessage, EMPTY_LOOKUPS, type MessageLookups } from '../../utils/messages.js';
|
|
26
26
|
import type { ModelAlias } from '../../utils/model/aliases.js';
|
|
27
27
|
import { getMainLoopModel, parseUserSpecifiedModel, renderModelName } from '../../utils/model/model.js';
|
|
28
28
|
import type { Theme, ThemeName } from '../../utils/theme.js';
|
|
@@ -32,6 +32,58 @@ import { getAgentColor } from './agentColorManager.js';
|
|
|
32
32
|
import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js';
|
|
33
33
|
const MAX_PROGRESS_MESSAGES_TO_SHOW = 3;
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Create a minimal fallback Tool object for a sub-agent tool name not present
|
|
37
|
+
* in the coordinator's tool set. Used in brain mode where the coordinator only
|
|
38
|
+
* has [Agent, AskUserQuestion] but sub-agents use Read/Grep/Bash/Edit/etc.
|
|
39
|
+
* Without fallbacks, findToolByName returns null and renders blank lines.
|
|
40
|
+
*/
|
|
41
|
+
function createFallbackTool(name: string): Tool {
|
|
42
|
+
return buildTool({
|
|
43
|
+
name,
|
|
44
|
+
inputSchema: z.object({}).passthrough(),
|
|
45
|
+
description: async () => `Sub-agent tool: ${name}`,
|
|
46
|
+
call: async () => ({ data: {} as unknown }) as any,
|
|
47
|
+
maxResultSizeChars: 100_000,
|
|
48
|
+
prompt: async () => '',
|
|
49
|
+
mapToolResultToToolResultBlockParam: (_content: unknown, toolUseID: string) => ({
|
|
50
|
+
tool_use_id: toolUseID,
|
|
51
|
+
content: [{ type: 'text' as const, text: '' }],
|
|
52
|
+
type: 'tool_result' as const,
|
|
53
|
+
}),
|
|
54
|
+
renderToolUseMessage: (input: Record<string, unknown>) => {
|
|
55
|
+
const keys = Object.keys(input);
|
|
56
|
+
if (keys.length === 0) return '';
|
|
57
|
+
return keys.map(k => {
|
|
58
|
+
const v = input[k];
|
|
59
|
+
let s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
60
|
+
if (s.length > 80) s = s.slice(0, 80) + '\u2026';
|
|
61
|
+
return `${k}: ${s}`;
|
|
62
|
+
}).join(', ');
|
|
63
|
+
},
|
|
64
|
+
renderToolResultMessage: () => null,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Given the result of buildSubagentLookups, create fallback Tool entries
|
|
70
|
+
* for any tool name not already in `tools`. Returns the original `tools`
|
|
71
|
+
* array if no new names are found. Avoids re-scanning progress data.
|
|
72
|
+
*/
|
|
73
|
+
function augmentToolsFromLookups(
|
|
74
|
+
tools: Tools,
|
|
75
|
+
lookups: MessageLookups,
|
|
76
|
+
): Tools {
|
|
77
|
+
const toolNames = new Set<string>()
|
|
78
|
+
for (const block of lookups.toolUseByToolUseID.values()) {
|
|
79
|
+
toolNames.add(block.name)
|
|
80
|
+
}
|
|
81
|
+
if (toolNames.size === 0) return tools
|
|
82
|
+
const missing = [...toolNames].filter(n => !tools.some(t => toolMatchesName(t, n)))
|
|
83
|
+
if (missing.length === 0) return tools
|
|
84
|
+
return [...tools, ...missing.map(createFallbackTool)]
|
|
85
|
+
}
|
|
86
|
+
|
|
35
87
|
/**
|
|
36
88
|
* Guard: checks if progress data has a `message` field (agent_progress or
|
|
37
89
|
* skill_progress). Other progress types (e.g. bash_progress forwarded from
|
|
@@ -262,12 +314,12 @@ function VerboseAgentTranscript(t0) {
|
|
|
262
314
|
lookups: agentLookups,
|
|
263
315
|
inProgressToolUseIDs
|
|
264
316
|
} = t1;
|
|
265
|
-
let t2;
|
|
317
|
+
const augmentedTools = augmentToolsFromLookups(tools, agentLookups); let t2;
|
|
266
318
|
if ($[2] !== agentLookups || $[3] !== inProgressToolUseIDs || $[4] !== progressMessages || $[5] !== tools || $[6] !== verbose) {
|
|
267
319
|
const filteredMessages = progressMessages.filter(_temp4);
|
|
268
320
|
let t3;
|
|
269
321
|
if ($[8] !== agentLookups || $[9] !== inProgressToolUseIDs || $[10] !== tools || $[11] !== verbose) {
|
|
270
|
-
t3 = progressMessage => <MessageResponse key={progressMessage.uuid} height={1}><MessageComponent message={progressMessage.data.message} lookups={agentLookups} addMargin={false} tools={
|
|
322
|
+
t3 = progressMessage => <MessageResponse key={progressMessage.uuid} height={1}><MessageComponent message={progressMessage.data.message} lookups={agentLookups} addMargin={false} tools={augmentedTools} commands={[]} verbose={verbose} inProgressToolUseIDs={inProgressToolUseIDs} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} isTranscriptMode={false} isStatic={true} /></MessageResponse>;
|
|
271
323
|
$[8] = agentLookups;
|
|
272
324
|
$[9] = inProgressToolUseIDs;
|
|
273
325
|
$[10] = tools;
|
|
@@ -384,6 +436,12 @@ export function renderToolResultMessage(data: Output, progressMessagesForMessage
|
|
|
384
436
|
speed: null
|
|
385
437
|
}
|
|
386
438
|
});
|
|
439
|
+
const agentProgressData = progressMessagesForMessage
|
|
440
|
+
.filter((pm): pm is ProgressMessage<AgentToolProgress> => hasProgressMessage(pm.data))
|
|
441
|
+
.map(pm => pm.data)
|
|
442
|
+
const { lookups: resultLookups } = buildSubagentLookups(agentProgressData)
|
|
443
|
+
const augmentedTools = augmentToolsFromLookups(tools, resultLookups)
|
|
444
|
+
|
|
387
445
|
return <Box flexDirection="column">
|
|
388
446
|
{"external" === 'ant' && <MessageResponse>
|
|
389
447
|
<Text color="warning">
|
|
@@ -394,13 +452,13 @@ export function renderToolResultMessage(data: Output, progressMessagesForMessage
|
|
|
394
452
|
<AgentPromptDisplay prompt={prompt} theme={theme} />
|
|
395
453
|
</MessageResponse>}
|
|
396
454
|
{isTranscriptMode ? <SubAgentProvider>
|
|
397
|
-
<VerboseAgentTranscript progressMessages={progressMessagesForMessage} tools={
|
|
455
|
+
<VerboseAgentTranscript progressMessages={progressMessagesForMessage} tools={augmentedTools} verbose={verbose} />
|
|
398
456
|
</SubAgentProvider> : null}
|
|
399
457
|
{isTranscriptMode && content && content.length > 0 && <MessageResponse>
|
|
400
458
|
<AgentResponseDisplay content={content} theme={theme} />
|
|
401
459
|
</MessageResponse>}
|
|
402
460
|
<MessageResponse height={1}>
|
|
403
|
-
<MessageComponent message={finalAssistantMessage} lookups={EMPTY_LOOKUPS} addMargin={false} tools={
|
|
461
|
+
<MessageComponent message={finalAssistantMessage} lookups={EMPTY_LOOKUPS} addMargin={false} tools={augmentedTools} commands={[]} verbose={verbose} inProgressToolUseIDs={new Set()} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} isTranscriptMode={false} isStatic={true} />
|
|
404
462
|
</MessageResponse>
|
|
405
463
|
{!isTranscriptMode && <Text dimColor>
|
|
406
464
|
{' '}
|
|
@@ -540,6 +598,13 @@ export function renderToolUseProgressMessage(progressMessages: ProgressMessage<P
|
|
|
540
598
|
lookups: subagentLookups,
|
|
541
599
|
inProgressToolUseIDs: collapsedInProgressIDs
|
|
542
600
|
} = buildSubagentLookups(progressMessages.filter((pm): pm is ProgressMessage<AgentToolProgress> => hasProgressMessage(pm.data)).map(pm => pm.data));
|
|
601
|
+
|
|
602
|
+
// Augment tools with fallback entries for sub-agent tools not in the
|
|
603
|
+
// coordinator's tool set. In brain mode the coordinator only has
|
|
604
|
+
// [Agent, AskUserQuestion], but sub-agents call Read/Grep/Bash/Edit/etc.
|
|
605
|
+
// Without fallbacks, findToolByName returns null and renders blank lines.
|
|
606
|
+
const augmentedTools = augmentToolsFromLookups(tools, subagentLookups);
|
|
607
|
+
|
|
543
608
|
return <MessageResponse>
|
|
544
609
|
<Box flexDirection="column">
|
|
545
610
|
<SubAgentProvider>
|
|
@@ -558,7 +623,7 @@ export function renderToolUseProgressMessage(progressMessages: ProgressMessage<P
|
|
|
558
623
|
// content (tool not found, renderToolUseMessage returns null)
|
|
559
624
|
// doesn't leave a blank line. Tool call headers are single-line
|
|
560
625
|
// anyway so truncation isn't needed.
|
|
561
|
-
return <MessageComponent key={processed.message.uuid} message={processed.message.data.message} lookups={subagentLookups} addMargin={false} tools={
|
|
626
|
+
return <MessageComponent key={processed.message.uuid} message={processed.message.data.message} lookups={subagentLookups} addMargin={false} tools={augmentedTools} commands={[]} verbose={verbose} inProgressToolUseIDs={collapsedInProgressIDs} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} style="condensed" isTranscriptMode={false} isStatic={true} />;
|
|
562
627
|
})}
|
|
563
628
|
</SubAgentProvider>
|
|
564
629
|
{hiddenToolUseCount > 0 && <Text dimColor>
|
package/src/utils/context.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { getCanonicalName } from './model/model.js'
|
|
|
6
6
|
import { getModelCapability } from './model/modelCapabilities.js'
|
|
7
7
|
|
|
8
8
|
// Model context window size (200k tokens for all models right now)
|
|
9
|
-
export const MODEL_CONTEXT_WINDOW_DEFAULT =
|
|
9
|
+
export const MODEL_CONTEXT_WINDOW_DEFAULT = 1_000_000
|
|
10
10
|
|
|
11
11
|
// Maximum output tokens for compact operations
|
|
12
12
|
export const COMPACT_MAX_OUTPUT_TOKENS = 20_000
|