@minniexcode/codex-switch 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.AI.md +141 -110
- package/README.CN.md +213 -179
- package/README.md +215 -183
- package/dist/app/add-provider.js +15 -23
- package/dist/app/edit-provider.js +30 -65
- package/dist/app/get-current-profile.js +15 -3
- package/dist/app/get-status.js +11 -8
- package/dist/app/list-config-profiles.js +3 -1
- package/dist/app/list-providers.js +10 -4
- package/dist/app/remove-provider.js +52 -19
- package/dist/app/run-doctor.js +26 -29
- package/dist/app/setup-codex.js +3 -3
- package/dist/app/show-config.js +3 -1
- package/dist/app/switch-provider.js +36 -5
- package/dist/cli/output.js +29 -19
- package/dist/commands/handlers.js +2 -2
- package/dist/commands/help.js +3 -3
- package/dist/commands/registry.js +29 -29
- package/dist/domain/config.js +249 -209
- package/dist/domain/providers.js +7 -0
- package/dist/domain/runtime-state.js +15 -15
- package/dist/domain/setup.js +3 -1
- package/dist/interaction/interactive.js +2 -2
- package/dist/runtime/codex-version.js +7 -0
- package/dist/storage/config-repo.js +6 -14
- package/docs/Design/codex-switch-v0.1.1-design.md +33 -0
- package/docs/Reference/codex-config-reference.md +41 -0
- package/docs/Reference/codex-config-reference.zh-CN.md +41 -0
- package/docs/cli-usage.md +282 -223
- package/docs/codex-switch-command-design.md +1 -1
- package/docs/codex-switch-product-overview.md +13 -12
- package/docs/codex-switch-technical-architecture.md +1 -1
- package/package.json +2 -2
package/dist/domain/config.js
CHANGED
|
@@ -47,22 +47,22 @@ exports.applyPatchOperations = applyPatchOperations;
|
|
|
47
47
|
const os = __importStar(require("node:os"));
|
|
48
48
|
const errors_1 = require("./errors");
|
|
49
49
|
/**
|
|
50
|
-
* Reads the
|
|
50
|
+
* Reads the legacy top-level profile selector from config.toml content.
|
|
51
51
|
*/
|
|
52
52
|
function parseTopLevelProfile(configContent) {
|
|
53
|
-
return parseStructuredConfig(configContent).
|
|
53
|
+
return parseStructuredConfig(configContent).legacyProfile;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Collects all named profile sections declared in config.toml content.
|
|
56
|
+
* Collects all named legacy profile sections declared in config.toml content.
|
|
57
57
|
*/
|
|
58
58
|
function parseProfileNames(configContent) {
|
|
59
59
|
return new Set(parseStructuredConfig(configContent).profiles.map((profile) => profile.name));
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
* Replaces or inserts the top-level profile assignment while preserving the rest of the file.
|
|
62
|
+
* Replaces or inserts the legacy top-level profile assignment while preserving the rest of the file.
|
|
63
63
|
*/
|
|
64
64
|
function replaceTopLevelProfile(configContent, profile) {
|
|
65
|
-
const plan = planConfigMutation(parseStructuredConfig(configContent), {
|
|
65
|
+
const plan = planConfigMutation(parseStructuredConfig(configContent), { setLegacyProfile: profile });
|
|
66
66
|
return applyPatchOperations(configContent, plan.operations);
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
@@ -71,12 +71,19 @@ function replaceTopLevelProfile(configContent, profile) {
|
|
|
71
71
|
function parseStructuredConfig(configContent) {
|
|
72
72
|
const lineEnding = configContent.includes("\r\n") ? "\r\n" : "\n";
|
|
73
73
|
const lines = splitWithOffsets(configContent);
|
|
74
|
-
let
|
|
75
|
-
let
|
|
74
|
+
let currentModel = null;
|
|
75
|
+
let currentModelRange = null;
|
|
76
|
+
let currentModelLineRange = null;
|
|
77
|
+
let currentModelProvider = null;
|
|
78
|
+
let currentModelProviderRange = null;
|
|
79
|
+
let currentModelProviderLineRange = null;
|
|
80
|
+
let legacyProfile = null;
|
|
81
|
+
let legacyProfileRange = null;
|
|
82
|
+
let legacyProfileLineRange = null;
|
|
76
83
|
const profiles = [];
|
|
77
84
|
const modelProviders = [];
|
|
78
85
|
let currentProfile = null;
|
|
79
|
-
let
|
|
86
|
+
let currentModelProviderSection = null;
|
|
80
87
|
let inRoot = true;
|
|
81
88
|
for (const line of lines) {
|
|
82
89
|
const trimmed = line.content.trim();
|
|
@@ -85,9 +92,9 @@ function parseStructuredConfig(configContent) {
|
|
|
85
92
|
if (currentProfile) {
|
|
86
93
|
currentProfile.sectionEnd = line.start;
|
|
87
94
|
}
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
if (currentModelProviderSection) {
|
|
96
|
+
currentModelProviderSection.sectionEnd = line.start;
|
|
97
|
+
currentModelProviderSection = null;
|
|
91
98
|
}
|
|
92
99
|
currentProfile = {
|
|
93
100
|
name: headerMatch[1],
|
|
@@ -110,10 +117,10 @@ function parseStructuredConfig(configContent) {
|
|
|
110
117
|
currentProfile.sectionEnd = line.start;
|
|
111
118
|
currentProfile = null;
|
|
112
119
|
}
|
|
113
|
-
if (
|
|
114
|
-
|
|
120
|
+
if (currentModelProviderSection) {
|
|
121
|
+
currentModelProviderSection.sectionEnd = line.start;
|
|
115
122
|
}
|
|
116
|
-
|
|
123
|
+
currentModelProviderSection = {
|
|
117
124
|
name: modelProviderHeaderMatch[1],
|
|
118
125
|
sectionStart: line.start,
|
|
119
126
|
sectionEnd: configContent.length,
|
|
@@ -126,8 +133,14 @@ function parseStructuredConfig(configContent) {
|
|
|
126
133
|
requiresOpenAiAuth: null,
|
|
127
134
|
wireApiValueRange: null,
|
|
128
135
|
wireApi: null,
|
|
136
|
+
envKeyValueRange: null,
|
|
137
|
+
envKey: null,
|
|
138
|
+
envKeyInstructionsValueRange: null,
|
|
139
|
+
envKeyInstructions: null,
|
|
140
|
+
envKeyLineRange: null,
|
|
141
|
+
envKeyInstructionsLineRange: null,
|
|
129
142
|
};
|
|
130
|
-
modelProviders.push(
|
|
143
|
+
modelProviders.push(currentModelProviderSection);
|
|
131
144
|
inRoot = false;
|
|
132
145
|
continue;
|
|
133
146
|
}
|
|
@@ -136,81 +149,92 @@ function parseStructuredConfig(configContent) {
|
|
|
136
149
|
currentProfile.sectionEnd = line.start;
|
|
137
150
|
currentProfile = null;
|
|
138
151
|
}
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
152
|
+
if (currentModelProviderSection) {
|
|
153
|
+
currentModelProviderSection.sectionEnd = line.start;
|
|
154
|
+
currentModelProviderSection = null;
|
|
142
155
|
}
|
|
143
156
|
inRoot = false;
|
|
144
157
|
continue;
|
|
145
158
|
}
|
|
146
159
|
if (inRoot) {
|
|
160
|
+
const modelMatch = matchKeyValueLine(line.content, "model");
|
|
161
|
+
if (modelMatch && !currentModel) {
|
|
162
|
+
currentModel = modelMatch.value;
|
|
163
|
+
currentModelRange = toAbsoluteRange(line.start, modelMatch.valueStart, modelMatch.valueEnd);
|
|
164
|
+
currentModelLineRange = { start: line.start, end: line.end };
|
|
165
|
+
}
|
|
166
|
+
const modelProviderMatch = matchKeyValueLine(line.content, "model_provider");
|
|
167
|
+
if (modelProviderMatch && !currentModelProvider) {
|
|
168
|
+
currentModelProvider = modelProviderMatch.value;
|
|
169
|
+
currentModelProviderRange = toAbsoluteRange(line.start, modelProviderMatch.valueStart, modelProviderMatch.valueEnd);
|
|
170
|
+
currentModelProviderLineRange = { start: line.start, end: line.end };
|
|
171
|
+
}
|
|
147
172
|
const profileMatch = matchKeyValueLine(line.content, "profile");
|
|
148
|
-
if (profileMatch && !
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
end: line.start + profileMatch.valueEnd,
|
|
153
|
-
};
|
|
173
|
+
if (profileMatch && !legacyProfile) {
|
|
174
|
+
legacyProfile = profileMatch.value;
|
|
175
|
+
legacyProfileRange = toAbsoluteRange(line.start, profileMatch.valueStart, profileMatch.valueEnd);
|
|
176
|
+
legacyProfileLineRange = { start: line.start, end: line.end };
|
|
154
177
|
}
|
|
155
178
|
}
|
|
156
179
|
if (currentProfile) {
|
|
157
180
|
const modelMatch = matchKeyValueLine(line.content, "model");
|
|
158
181
|
if (modelMatch) {
|
|
159
182
|
currentProfile.model = modelMatch.value;
|
|
160
|
-
currentProfile.modelValueRange =
|
|
161
|
-
start: line.start + modelMatch.valueStart,
|
|
162
|
-
end: line.start + modelMatch.valueEnd,
|
|
163
|
-
};
|
|
183
|
+
currentProfile.modelValueRange = toAbsoluteRange(line.start, modelMatch.valueStart, modelMatch.valueEnd);
|
|
164
184
|
}
|
|
165
185
|
const modelProviderMatch = matchKeyValueLine(line.content, "model_provider");
|
|
166
186
|
if (modelProviderMatch) {
|
|
167
187
|
currentProfile.modelProvider = modelProviderMatch.value;
|
|
168
|
-
currentProfile.modelProviderValueRange =
|
|
169
|
-
start: line.start + modelProviderMatch.valueStart,
|
|
170
|
-
end: line.start + modelProviderMatch.valueEnd,
|
|
171
|
-
};
|
|
188
|
+
currentProfile.modelProviderValueRange = toAbsoluteRange(line.start, modelProviderMatch.valueStart, modelProviderMatch.valueEnd);
|
|
172
189
|
}
|
|
173
190
|
}
|
|
174
|
-
if (
|
|
191
|
+
if (currentModelProviderSection) {
|
|
175
192
|
const baseUrlMatch = matchKeyValueLine(line.content, "base_url");
|
|
176
193
|
if (baseUrlMatch) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
start: line.start + baseUrlMatch.valueStart,
|
|
180
|
-
end: line.start + baseUrlMatch.valueEnd,
|
|
181
|
-
};
|
|
194
|
+
currentModelProviderSection.baseUrl = baseUrlMatch.value;
|
|
195
|
+
currentModelProviderSection.baseUrlValueRange = toAbsoluteRange(line.start, baseUrlMatch.valueStart, baseUrlMatch.valueEnd);
|
|
182
196
|
}
|
|
183
197
|
const nameMatch = matchKeyValueLine(line.content, "name");
|
|
184
198
|
if (nameMatch) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
start: line.start + nameMatch.valueStart,
|
|
188
|
-
end: line.start + nameMatch.valueEnd,
|
|
189
|
-
};
|
|
199
|
+
currentModelProviderSection.providerName = nameMatch.value;
|
|
200
|
+
currentModelProviderSection.nameValueRange = toAbsoluteRange(line.start, nameMatch.valueStart, nameMatch.valueEnd);
|
|
190
201
|
}
|
|
191
202
|
const requiresOpenAiAuthMatch = matchBooleanKeyValueLine(line.content, "requires_openai_auth");
|
|
192
203
|
if (requiresOpenAiAuthMatch) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
start: line.start + requiresOpenAiAuthMatch.valueStart,
|
|
196
|
-
end: line.start + requiresOpenAiAuthMatch.valueEnd,
|
|
197
|
-
};
|
|
204
|
+
currentModelProviderSection.requiresOpenAiAuth = requiresOpenAiAuthMatch.value;
|
|
205
|
+
currentModelProviderSection.requiresOpenAiAuthValueRange = toAbsoluteRange(line.start, requiresOpenAiAuthMatch.valueStart, requiresOpenAiAuthMatch.valueEnd);
|
|
198
206
|
}
|
|
199
207
|
const wireApiMatch = matchKeyValueLine(line.content, "wire_api");
|
|
200
208
|
if (wireApiMatch) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
currentModelProviderSection.wireApi = wireApiMatch.value;
|
|
210
|
+
currentModelProviderSection.wireApiValueRange = toAbsoluteRange(line.start, wireApiMatch.valueStart, wireApiMatch.valueEnd);
|
|
211
|
+
}
|
|
212
|
+
const envKeyMatch = matchKeyValueLine(line.content, "env_key");
|
|
213
|
+
if (envKeyMatch) {
|
|
214
|
+
currentModelProviderSection.envKey = envKeyMatch.value;
|
|
215
|
+
currentModelProviderSection.envKeyValueRange = toAbsoluteRange(line.start, envKeyMatch.valueStart, envKeyMatch.valueEnd);
|
|
216
|
+
currentModelProviderSection.envKeyLineRange = { start: line.start, end: line.end };
|
|
217
|
+
}
|
|
218
|
+
const envKeyInstructionsMatch = matchKeyValueLine(line.content, "env_key_instructions");
|
|
219
|
+
if (envKeyInstructionsMatch) {
|
|
220
|
+
currentModelProviderSection.envKeyInstructions = envKeyInstructionsMatch.value;
|
|
221
|
+
currentModelProviderSection.envKeyInstructionsValueRange = toAbsoluteRange(line.start, envKeyInstructionsMatch.valueStart, envKeyInstructionsMatch.valueEnd);
|
|
222
|
+
currentModelProviderSection.envKeyInstructionsLineRange = { start: line.start, end: line.end };
|
|
206
223
|
}
|
|
207
224
|
}
|
|
208
225
|
}
|
|
209
226
|
return {
|
|
210
227
|
rawText: configContent,
|
|
211
228
|
lineEnding,
|
|
212
|
-
|
|
213
|
-
|
|
229
|
+
currentModel,
|
|
230
|
+
currentModelRange,
|
|
231
|
+
currentModelLineRange,
|
|
232
|
+
currentModelProvider,
|
|
233
|
+
currentModelProviderRange,
|
|
234
|
+
currentModelProviderLineRange,
|
|
235
|
+
legacyProfile,
|
|
236
|
+
legacyProfileRange,
|
|
237
|
+
legacyProfileLineRange,
|
|
214
238
|
profiles: profiles.map((profile) => ({
|
|
215
239
|
...profile,
|
|
216
240
|
managedFieldInsertIndex: findManagedFieldInsertIndex(configContent, profile.sectionStart, profile.sectionEnd),
|
|
@@ -222,7 +246,7 @@ function parseStructuredConfig(configContent) {
|
|
|
222
246
|
};
|
|
223
247
|
}
|
|
224
248
|
/**
|
|
225
|
-
* Builds the
|
|
249
|
+
* Builds the legacy profile inspection views used by config commands and diagnostics.
|
|
226
250
|
*/
|
|
227
251
|
function buildManagedProfileViews(document, providers) {
|
|
228
252
|
const linkMap = buildProfileLinkMap(providers);
|
|
@@ -236,7 +260,7 @@ function buildManagedProfileViews(document, providers) {
|
|
|
236
260
|
views.push({
|
|
237
261
|
name: section.name,
|
|
238
262
|
managed: linkInfo.managed,
|
|
239
|
-
isActive: document.
|
|
263
|
+
isActive: document.currentModelProvider === section.name,
|
|
240
264
|
linkedProviders: [...linkInfo.linkedProviders].sort(),
|
|
241
265
|
model: section.model,
|
|
242
266
|
modelProvider: section.modelProvider,
|
|
@@ -252,7 +276,7 @@ function buildManagedProfileViews(document, providers) {
|
|
|
252
276
|
views.push({
|
|
253
277
|
name: profile,
|
|
254
278
|
managed: true,
|
|
255
|
-
isActive: document.
|
|
279
|
+
isActive: document.currentModelProvider === profile,
|
|
256
280
|
linkedProviders: [...linkInfo.linkedProviders].sort(),
|
|
257
281
|
model: null,
|
|
258
282
|
modelProvider: null,
|
|
@@ -269,109 +293,60 @@ function buildManagedProfileViews(document, providers) {
|
|
|
269
293
|
function collectConfigConsistencyIssues(document, providers) {
|
|
270
294
|
const issues = [];
|
|
271
295
|
const providerMap = providers?.providers ?? null;
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
const activeModelProvider = document.currentModelProvider;
|
|
297
|
+
const activeProviderSection = activeModelProvider
|
|
298
|
+
? document.modelProviders.find((entry) => entry.name === activeModelProvider) ?? null
|
|
299
|
+
: null;
|
|
300
|
+
if (!document.currentModel) {
|
|
301
|
+
issues.push({ code: "MODEL_MISSING", modelProvider: activeModelProvider ?? "(none)" });
|
|
302
|
+
}
|
|
303
|
+
if (!document.currentModelProvider) {
|
|
304
|
+
issues.push({ code: "MODEL_PROVIDER_MISSING" });
|
|
305
|
+
}
|
|
306
|
+
if (document.legacyProfile) {
|
|
307
|
+
issues.push({ code: "LEGACY_PROFILE_SELECTOR", profile: document.legacyProfile });
|
|
308
|
+
}
|
|
309
|
+
for (const profile of document.profiles) {
|
|
310
|
+
issues.push({ code: "LEGACY_PROFILE_SECTION", profile: profile.name });
|
|
311
|
+
}
|
|
312
|
+
if (activeModelProvider && !activeProviderSection) {
|
|
313
|
+
issues.push({ code: "MODEL_PROVIDER_SECTION_MISSING", modelProvider: activeModelProvider });
|
|
314
|
+
}
|
|
315
|
+
if (activeModelProvider && activeProviderSection && !activeProviderSection.baseUrl) {
|
|
316
|
+
issues.push({ code: "MODEL_PROVIDER_BASE_URL_MISSING", modelProvider: activeModelProvider });
|
|
317
|
+
}
|
|
318
|
+
if (activeProviderSection?.envKey) {
|
|
319
|
+
issues.push({
|
|
320
|
+
code: "LEGACY_MODEL_PROVIDER_ENV_KEY",
|
|
321
|
+
modelProvider: activeProviderSection.name,
|
|
322
|
+
envKey: activeProviderSection.envKey,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (activeModelProvider && providerMap) {
|
|
326
|
+
const linkedProviders = Object.entries(providerMap)
|
|
327
|
+
.filter(([, provider]) => provider.profile === activeModelProvider)
|
|
328
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
329
|
+
if (linkedProviders.length === 1 && activeProviderSection?.baseUrl) {
|
|
330
|
+
const [providerName, provider] = linkedProviders[0];
|
|
331
|
+
if (!provider.runtime &&
|
|
332
|
+
typeof provider.baseUrl === "string" &&
|
|
333
|
+
provider.baseUrl.trim() !== "" &&
|
|
334
|
+
provider.baseUrl !== activeProviderSection.baseUrl) {
|
|
296
335
|
issues.push({
|
|
297
|
-
code: "
|
|
298
|
-
|
|
336
|
+
code: "PROVIDER_BASE_URL_MISMATCH",
|
|
337
|
+
modelProvider: activeModelProvider,
|
|
338
|
+
provider: providerName,
|
|
339
|
+
providerBaseUrl: provider.baseUrl,
|
|
340
|
+
configBaseUrl: activeProviderSection.baseUrl,
|
|
341
|
+
providerType: "direct",
|
|
299
342
|
});
|
|
300
343
|
}
|
|
301
|
-
else {
|
|
302
|
-
if (view.modelProvider !== view.name) {
|
|
303
|
-
issues.push({
|
|
304
|
-
code: "MODEL_PROVIDER_NAME_MISMATCH",
|
|
305
|
-
profile: view.name,
|
|
306
|
-
modelProvider: view.modelProvider,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
const modelProviderSection = document.modelProviders.find((entry) => entry.name === view.modelProvider);
|
|
310
|
-
if (!modelProviderSection) {
|
|
311
|
-
issues.push({
|
|
312
|
-
code: "MODEL_PROVIDER_SECTION_MISSING",
|
|
313
|
-
profile: view.name,
|
|
314
|
-
modelProvider: view.modelProvider,
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
else if (!modelProviderSection.baseUrl) {
|
|
318
|
-
issues.push({
|
|
319
|
-
code: "MODEL_PROVIDER_BASE_URL_MISSING",
|
|
320
|
-
profile: view.name,
|
|
321
|
-
modelProvider: view.modelProvider,
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
const profileLinkInfo = profileLinkMap.get(view.name);
|
|
326
|
-
if (profileLinkInfo &&
|
|
327
|
-
profileLinkInfo.linkedProviders.length === 1 &&
|
|
328
|
-
providerMap) {
|
|
329
|
-
const providerName = profileLinkInfo.linkedProviders[0];
|
|
330
|
-
const provider = providerMap[providerName];
|
|
331
|
-
if (provider &&
|
|
332
|
-
!provider.runtime &&
|
|
333
|
-
typeof provider.baseUrl === "string" &&
|
|
334
|
-
provider.baseUrl.trim() !== "" &&
|
|
335
|
-
provider.baseUrl !== modelProviderSection.baseUrl) {
|
|
336
|
-
issues.push({
|
|
337
|
-
code: "PROVIDER_BASE_URL_MISMATCH",
|
|
338
|
-
profile: view.name,
|
|
339
|
-
provider: providerName,
|
|
340
|
-
providerBaseUrl: provider.baseUrl,
|
|
341
|
-
configBaseUrl: modelProviderSection.baseUrl,
|
|
342
|
-
providerType: "direct",
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (document.activeProfile) {
|
|
351
|
-
const activeLinkInfo = profileLinkMap.get(document.activeProfile);
|
|
352
|
-
if (!activeLinkInfo) {
|
|
353
|
-
issues.push({
|
|
354
|
-
code: "UNMANAGED_ACTIVE_PROFILE",
|
|
355
|
-
profile: document.activeProfile,
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
else if (activeLinkInfo.linkedProviders.length > 1) {
|
|
359
|
-
issues.push({
|
|
360
|
-
code: "ACTIVE_PROVIDER_UNRESOLVED",
|
|
361
|
-
profile: document.activeProfile,
|
|
362
|
-
providers: [...activeLinkInfo.linkedProviders],
|
|
363
|
-
});
|
|
364
344
|
}
|
|
365
345
|
}
|
|
366
|
-
return issues.sort((left, right) =>
|
|
367
|
-
if (left.profile === right.profile) {
|
|
368
|
-
return left.code.localeCompare(right.code);
|
|
369
|
-
}
|
|
370
|
-
return left.profile.localeCompare(right.profile);
|
|
371
|
-
});
|
|
346
|
+
return issues.sort((left, right) => left.code.localeCompare(right.code));
|
|
372
347
|
}
|
|
373
348
|
/**
|
|
374
|
-
* Ensures the minimal managed profile fields are available before a new section is created.
|
|
349
|
+
* Ensures the minimal managed profile fields are available before a new legacy section is created.
|
|
375
350
|
*/
|
|
376
351
|
function validateManagedProfileCreation(profile, fields) {
|
|
377
352
|
const model = fields.model?.trim() ?? "";
|
|
@@ -391,7 +366,7 @@ function validateManagedProfileCreation(profile, fields) {
|
|
|
391
366
|
};
|
|
392
367
|
}
|
|
393
368
|
/**
|
|
394
|
-
* Computes keep/delete/switch outcomes when a provider leaves or changes
|
|
369
|
+
* Computes keep/delete/switch outcomes when a provider leaves or changes model-provider bindings.
|
|
395
370
|
*/
|
|
396
371
|
function planProfileLifecycleOutcome(args) {
|
|
397
372
|
if (!args.oldProfile || args.oldProfile === args.newProfile) {
|
|
@@ -435,7 +410,7 @@ function planProfileLifecycleOutcome(args) {
|
|
|
435
410
|
};
|
|
436
411
|
}
|
|
437
412
|
/**
|
|
438
|
-
* Builds a text patch plan for
|
|
413
|
+
* Builds a text patch plan for route fields, legacy selectors, and provider-section mutations.
|
|
439
414
|
*/
|
|
440
415
|
function planConfigMutation(document, args) {
|
|
441
416
|
const operations = [];
|
|
@@ -446,27 +421,19 @@ function planConfigMutation(document, args) {
|
|
|
446
421
|
const updatedModelProviders = [];
|
|
447
422
|
const sectionMap = new Map(document.profiles.map((profile) => [profile.name, profile]));
|
|
448
423
|
const modelProviderSectionMap = new Map(document.modelProviders.map((entry) => [entry.name, entry]));
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const insertAt = findTopLevelInsertIndex(document.rawText);
|
|
461
|
-
const text = `profile = ${quoted}${document.lineEnding}`;
|
|
462
|
-
operations.push({
|
|
463
|
-
kind: "insert-at",
|
|
464
|
-
index: insertAt,
|
|
465
|
-
text,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
424
|
+
planRootFieldMutation(document, "model", document.currentModel, document.currentModelRange, document.currentModelLineRange, args.setCurrentModel, operations);
|
|
425
|
+
planRootFieldMutation(document, "model_provider", document.currentModelProvider, document.currentModelProviderRange, document.currentModelProviderLineRange, args.setCurrentModelProvider, operations);
|
|
426
|
+
if (args.setLegacyProfile !== undefined) {
|
|
427
|
+
planRootFieldMutation(document, "profile", document.legacyProfile, document.legacyProfileRange, document.legacyProfileLineRange, args.setLegacyProfile, operations);
|
|
428
|
+
}
|
|
429
|
+
if (args.deleteLegacyProfile && document.legacyProfileLineRange) {
|
|
430
|
+
operations.push({
|
|
431
|
+
kind: "delete-range",
|
|
432
|
+
start: document.legacyProfileLineRange.start,
|
|
433
|
+
end: expandLineDeletionStart(document.rawText, document.legacyProfileLineRange.start, document.legacyProfileLineRange.end),
|
|
434
|
+
});
|
|
468
435
|
}
|
|
469
|
-
for (const profileName of args.deleteProfiles ?? []) {
|
|
436
|
+
for (const profileName of [...(args.deleteProfiles ?? []), ...(args.deleteLegacyProfilesByName ?? [])]) {
|
|
470
437
|
const section = sectionMap.get(profileName);
|
|
471
438
|
if (!section) {
|
|
472
439
|
continue;
|
|
@@ -503,39 +470,47 @@ function planConfigMutation(document, args) {
|
|
|
503
470
|
for (const [profileName, fields] of Object.entries(args.upsertModelProviders ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
504
471
|
const section = modelProviderSectionMap.get(profileName);
|
|
505
472
|
if (!section) {
|
|
506
|
-
const
|
|
507
|
-
const providerName = fields.name?.trim() ?? "";
|
|
508
|
-
if (!baseUrl) {
|
|
509
|
-
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${profileName}" requires base_url.`, {
|
|
510
|
-
profile: profileName,
|
|
511
|
-
modelProvider: profileName,
|
|
512
|
-
missingFields: [
|
|
513
|
-
!baseUrl ? "base_url" : null,
|
|
514
|
-
].filter((value) => Boolean(value)),
|
|
515
|
-
});
|
|
516
|
-
}
|
|
473
|
+
const normalizedFields = normalizeManagedModelProviderFields(profileName, fields);
|
|
517
474
|
const prefix = document.rawText.length > 0 && !document.rawText.endsWith(document.lineEnding)
|
|
518
475
|
? document.lineEnding
|
|
519
476
|
: "";
|
|
520
|
-
const requiresOpenAiAuth = fields.requiresOpenAiAuth;
|
|
521
|
-
const wireApi = fields.wireApi?.trim() ?? "";
|
|
522
477
|
operations.push({
|
|
523
478
|
kind: "insert-at",
|
|
524
479
|
index: document.rawText.length,
|
|
525
480
|
text: `${prefix}[model_providers.${profileName}]${document.lineEnding}` +
|
|
526
|
-
`base_url = ${JSON.stringify(baseUrl)}${document.lineEnding}` +
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
481
|
+
`base_url = ${JSON.stringify(normalizedFields.baseUrl)}${document.lineEnding}` +
|
|
482
|
+
`name = ${JSON.stringify(normalizedFields.name)}${document.lineEnding}` +
|
|
483
|
+
`requires_openai_auth = ${String(normalizedFields.requiresOpenAiAuth)}${document.lineEnding}` +
|
|
484
|
+
`wire_api = ${JSON.stringify(normalizedFields.wireApi)}${document.lineEnding}`,
|
|
530
485
|
});
|
|
531
486
|
createdModelProviderSections.push(profileName);
|
|
532
487
|
continue;
|
|
533
488
|
}
|
|
534
|
-
const sectionUpdated = planModelProviderFieldMutation(section, fields, operations);
|
|
489
|
+
const sectionUpdated = planModelProviderFieldMutation(document, section, normalizeManagedModelProviderFields(profileName, fields), operations);
|
|
535
490
|
if (sectionUpdated) {
|
|
536
491
|
updatedModelProviders.push(profileName);
|
|
537
492
|
}
|
|
538
493
|
}
|
|
494
|
+
for (const profileName of args.scrubModelProviderEnvKeys ?? []) {
|
|
495
|
+
const section = modelProviderSectionMap.get(profileName);
|
|
496
|
+
if (!section) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (section.envKeyLineRange) {
|
|
500
|
+
operations.push({
|
|
501
|
+
kind: "delete-range",
|
|
502
|
+
start: section.envKeyLineRange.start,
|
|
503
|
+
end: expandLineDeletionStart(document.rawText, section.envKeyLineRange.start, section.envKeyLineRange.end),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
if (section.envKeyInstructionsLineRange) {
|
|
507
|
+
operations.push({
|
|
508
|
+
kind: "delete-range",
|
|
509
|
+
start: section.envKeyInstructionsLineRange.start,
|
|
510
|
+
end: expandLineDeletionStart(document.rawText, section.envKeyInstructionsLineRange.start, section.envKeyInstructionsLineRange.end),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
539
514
|
return {
|
|
540
515
|
operations,
|
|
541
516
|
createdProfileSections,
|
|
@@ -543,7 +518,8 @@ function planConfigMutation(document, args) {
|
|
|
543
518
|
deletedProfileSections,
|
|
544
519
|
updatedProfiles,
|
|
545
520
|
updatedModelProviders,
|
|
546
|
-
switchedActiveProfile: Boolean(args.
|
|
521
|
+
switchedActiveProfile: Boolean(args.setCurrentModelProvider !== undefined && args.setCurrentModelProvider !== document.currentModelProvider) ||
|
|
522
|
+
Boolean(args.setLegacyProfile !== undefined && args.setLegacyProfile !== document.legacyProfile),
|
|
547
523
|
};
|
|
548
524
|
}
|
|
549
525
|
/**
|
|
@@ -565,6 +541,9 @@ function applyPatchOperations(rawText, operations) {
|
|
|
565
541
|
}
|
|
566
542
|
return nextText;
|
|
567
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Plans managed field updates for one legacy profile section.
|
|
546
|
+
*/
|
|
568
547
|
function planSectionFieldMutation(document, section, fields, operations) {
|
|
569
548
|
let updated = false;
|
|
570
549
|
const modelText = fields.model !== undefined ? JSON.stringify(fields.model) : null;
|
|
@@ -612,14 +591,14 @@ function planSectionFieldMutation(document, section, fields, operations) {
|
|
|
612
591
|
/**
|
|
613
592
|
* Plans managed field updates for one model_providers section.
|
|
614
593
|
*/
|
|
615
|
-
function planModelProviderFieldMutation(section, fields, operations) {
|
|
594
|
+
function planModelProviderFieldMutation(document, section, fields, operations) {
|
|
616
595
|
let updated = false;
|
|
617
|
-
const baseUrlText =
|
|
618
|
-
const nameText =
|
|
619
|
-
const requiresOpenAiAuthText =
|
|
620
|
-
const wireApiText =
|
|
596
|
+
const baseUrlText = JSON.stringify(fields.baseUrl);
|
|
597
|
+
const nameText = JSON.stringify(fields.name);
|
|
598
|
+
const requiresOpenAiAuthText = String(fields.requiresOpenAiAuth);
|
|
599
|
+
const wireApiText = JSON.stringify(fields.wireApi);
|
|
621
600
|
const inserts = [];
|
|
622
|
-
if (
|
|
601
|
+
if (section.baseUrlValueRange) {
|
|
623
602
|
if (section.baseUrl !== fields.baseUrl) {
|
|
624
603
|
operations.push({
|
|
625
604
|
kind: "replace-range",
|
|
@@ -630,11 +609,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
630
609
|
updated = true;
|
|
631
610
|
}
|
|
632
611
|
}
|
|
633
|
-
else
|
|
612
|
+
else {
|
|
634
613
|
inserts.push(`base_url = ${baseUrlText}`);
|
|
635
614
|
updated = true;
|
|
636
615
|
}
|
|
637
|
-
if (
|
|
616
|
+
if (section.nameValueRange) {
|
|
638
617
|
if (section.providerName !== fields.name) {
|
|
639
618
|
operations.push({
|
|
640
619
|
kind: "replace-range",
|
|
@@ -645,11 +624,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
645
624
|
updated = true;
|
|
646
625
|
}
|
|
647
626
|
}
|
|
648
|
-
else
|
|
627
|
+
else {
|
|
649
628
|
inserts.push(`name = ${nameText}`);
|
|
650
629
|
updated = true;
|
|
651
630
|
}
|
|
652
|
-
if (
|
|
631
|
+
if (section.requiresOpenAiAuthValueRange) {
|
|
653
632
|
if (section.requiresOpenAiAuth !== fields.requiresOpenAiAuth) {
|
|
654
633
|
operations.push({
|
|
655
634
|
kind: "replace-range",
|
|
@@ -660,11 +639,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
660
639
|
updated = true;
|
|
661
640
|
}
|
|
662
641
|
}
|
|
663
|
-
else
|
|
642
|
+
else {
|
|
664
643
|
inserts.push(`requires_openai_auth = ${requiresOpenAiAuthText}`);
|
|
665
644
|
updated = true;
|
|
666
645
|
}
|
|
667
|
-
if (
|
|
646
|
+
if (section.wireApiValueRange) {
|
|
668
647
|
if (section.wireApi !== fields.wireApi) {
|
|
669
648
|
operations.push({
|
|
670
649
|
kind: "replace-range",
|
|
@@ -675,7 +654,7 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
675
654
|
updated = true;
|
|
676
655
|
}
|
|
677
656
|
}
|
|
678
|
-
else
|
|
657
|
+
else {
|
|
679
658
|
inserts.push(`wire_api = ${wireApiText}`);
|
|
680
659
|
updated = true;
|
|
681
660
|
}
|
|
@@ -683,11 +662,59 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
683
662
|
operations.push({
|
|
684
663
|
kind: "insert-at",
|
|
685
664
|
index: section.managedFieldInsertIndex,
|
|
686
|
-
text: `${inserts.join(
|
|
665
|
+
text: `${inserts.join(document.lineEnding)}${document.lineEnding}`,
|
|
687
666
|
});
|
|
688
667
|
}
|
|
689
668
|
return updated;
|
|
690
669
|
}
|
|
670
|
+
function planRootFieldMutation(document, key, currentValue, currentValueRange, currentLineRange, nextValue, operations) {
|
|
671
|
+
if (nextValue === undefined) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (nextValue === null) {
|
|
675
|
+
if (currentLineRange) {
|
|
676
|
+
operations.push({
|
|
677
|
+
kind: "delete-range",
|
|
678
|
+
start: currentLineRange.start,
|
|
679
|
+
end: expandLineDeletionStart(document.rawText, currentLineRange.start, currentLineRange.end),
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (currentValueRange) {
|
|
685
|
+
if (currentValue !== nextValue) {
|
|
686
|
+
operations.push({
|
|
687
|
+
kind: "replace-range",
|
|
688
|
+
start: currentValueRange.start,
|
|
689
|
+
end: currentValueRange.end,
|
|
690
|
+
text: JSON.stringify(nextValue),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const insertAt = findTopLevelInsertIndex(document.rawText);
|
|
696
|
+
operations.push({
|
|
697
|
+
kind: "insert-at",
|
|
698
|
+
index: insertAt,
|
|
699
|
+
text: `${key} = ${JSON.stringify(nextValue)}${document.lineEnding}`,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
function normalizeManagedModelProviderFields(profileName, fields) {
|
|
703
|
+
const baseUrl = fields.baseUrl?.trim() ?? "";
|
|
704
|
+
if (!baseUrl) {
|
|
705
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${profileName}" requires base_url.`, {
|
|
706
|
+
profile: profileName,
|
|
707
|
+
modelProvider: profileName,
|
|
708
|
+
missingFields: ["base_url"],
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
baseUrl,
|
|
713
|
+
name: fields.name?.trim() || profileName,
|
|
714
|
+
requiresOpenAiAuth: fields.requiresOpenAiAuth ?? true,
|
|
715
|
+
wireApi: fields.wireApi?.trim() || "responses",
|
|
716
|
+
};
|
|
717
|
+
}
|
|
691
718
|
function splitWithOffsets(value) {
|
|
692
719
|
if (value.length === 0) {
|
|
693
720
|
return [];
|
|
@@ -812,6 +839,19 @@ function expandDeletionEnd(rawText, sectionStart, sectionEnd) {
|
|
|
812
839
|
}
|
|
813
840
|
return end;
|
|
814
841
|
}
|
|
842
|
+
function expandLineDeletionStart(rawText, start, end) {
|
|
843
|
+
let nextEnd = end;
|
|
844
|
+
while (nextEnd < rawText.length && (rawText[nextEnd] === "\r" || rawText[nextEnd] === "\n")) {
|
|
845
|
+
nextEnd += 1;
|
|
846
|
+
}
|
|
847
|
+
return nextEnd;
|
|
848
|
+
}
|
|
849
|
+
function toAbsoluteRange(lineStart, valueStart, valueEnd) {
|
|
850
|
+
return {
|
|
851
|
+
start: lineStart + valueStart,
|
|
852
|
+
end: lineStart + valueEnd,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
815
855
|
function escapeRegExp(value) {
|
|
816
856
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
817
857
|
}
|