@minniexcode/codex-switch 0.0.12 → 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 +37 -6
- package/README.CN.md +45 -11
- package/README.md +45 -13
- package/dist/app/add-provider.js +22 -24
- package/dist/app/edit-provider.js +34 -55
- 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 +29 -28
- 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 +36 -18
- package/dist/commands/handlers.js +2 -2
- package/dist/commands/help.js +3 -3
- package/dist/commands/registry.js +35 -30
- package/dist/domain/config.js +250 -185
- package/dist/domain/providers.js +23 -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.0-design.md +152 -0
- package/docs/Design/codex-switch-v0.1.1-design.md +33 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +217 -205
- package/docs/Reference/codex-config-reference.md +41 -0
- package/docs/Reference/codex-config-reference.zh-CN.md +41 -0
- package/docs/Tests/testing.md +31 -78
- package/docs/cli-usage.md +86 -27
- package/docs/codex-switch-command-design.md +649 -649
- package/docs/codex-switch-product-overview.md +81 -80
- package/docs/codex-switch-technical-architecture.md +1115 -1115
- package/package.json +51 -51
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,
|
|
@@ -268,85 +292,61 @@ function buildManagedProfileViews(document, providers) {
|
|
|
268
292
|
*/
|
|
269
293
|
function collectConfigConsistencyIssues(document, providers) {
|
|
270
294
|
const issues = [];
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
295
|
+
const providerMap = providers?.providers ?? null;
|
|
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) {
|
|
294
335
|
issues.push({
|
|
295
|
-
code: "
|
|
296
|
-
|
|
336
|
+
code: "PROVIDER_BASE_URL_MISMATCH",
|
|
337
|
+
modelProvider: activeModelProvider,
|
|
338
|
+
provider: providerName,
|
|
339
|
+
providerBaseUrl: provider.baseUrl,
|
|
340
|
+
configBaseUrl: activeProviderSection.baseUrl,
|
|
341
|
+
providerType: "direct",
|
|
297
342
|
});
|
|
298
343
|
}
|
|
299
|
-
else {
|
|
300
|
-
if (view.modelProvider !== view.name) {
|
|
301
|
-
issues.push({
|
|
302
|
-
code: "MODEL_PROVIDER_NAME_MISMATCH",
|
|
303
|
-
profile: view.name,
|
|
304
|
-
modelProvider: view.modelProvider,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
const modelProviderSection = document.modelProviders.find((entry) => entry.name === view.modelProvider);
|
|
308
|
-
if (!modelProviderSection) {
|
|
309
|
-
issues.push({
|
|
310
|
-
code: "MODEL_PROVIDER_SECTION_MISSING",
|
|
311
|
-
profile: view.name,
|
|
312
|
-
modelProvider: view.modelProvider,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
else if (!modelProviderSection.baseUrl) {
|
|
316
|
-
issues.push({
|
|
317
|
-
code: "MODEL_PROVIDER_BASE_URL_MISSING",
|
|
318
|
-
profile: view.name,
|
|
319
|
-
modelProvider: view.modelProvider,
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
344
|
}
|
|
324
345
|
}
|
|
325
|
-
|
|
326
|
-
const activeLinkInfo = buildProfileLinkMap(providers).get(document.activeProfile);
|
|
327
|
-
if (!activeLinkInfo) {
|
|
328
|
-
issues.push({
|
|
329
|
-
code: "UNMANAGED_ACTIVE_PROFILE",
|
|
330
|
-
profile: document.activeProfile,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
else if (activeLinkInfo.linkedProviders.length > 1) {
|
|
334
|
-
issues.push({
|
|
335
|
-
code: "ACTIVE_PROVIDER_UNRESOLVED",
|
|
336
|
-
profile: document.activeProfile,
|
|
337
|
-
providers: [...activeLinkInfo.linkedProviders],
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return issues.sort((left, right) => {
|
|
342
|
-
if (left.profile === right.profile) {
|
|
343
|
-
return left.code.localeCompare(right.code);
|
|
344
|
-
}
|
|
345
|
-
return left.profile.localeCompare(right.profile);
|
|
346
|
-
});
|
|
346
|
+
return issues.sort((left, right) => left.code.localeCompare(right.code));
|
|
347
347
|
}
|
|
348
348
|
/**
|
|
349
|
-
* 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.
|
|
350
350
|
*/
|
|
351
351
|
function validateManagedProfileCreation(profile, fields) {
|
|
352
352
|
const model = fields.model?.trim() ?? "";
|
|
@@ -366,7 +366,7 @@ function validateManagedProfileCreation(profile, fields) {
|
|
|
366
366
|
};
|
|
367
367
|
}
|
|
368
368
|
/**
|
|
369
|
-
* 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.
|
|
370
370
|
*/
|
|
371
371
|
function planProfileLifecycleOutcome(args) {
|
|
372
372
|
if (!args.oldProfile || args.oldProfile === args.newProfile) {
|
|
@@ -410,7 +410,7 @@ function planProfileLifecycleOutcome(args) {
|
|
|
410
410
|
};
|
|
411
411
|
}
|
|
412
412
|
/**
|
|
413
|
-
* Builds a text patch plan for
|
|
413
|
+
* Builds a text patch plan for route fields, legacy selectors, and provider-section mutations.
|
|
414
414
|
*/
|
|
415
415
|
function planConfigMutation(document, args) {
|
|
416
416
|
const operations = [];
|
|
@@ -421,27 +421,19 @@ function planConfigMutation(document, args) {
|
|
|
421
421
|
const updatedModelProviders = [];
|
|
422
422
|
const sectionMap = new Map(document.profiles.map((profile) => [profile.name, profile]));
|
|
423
423
|
const modelProviderSectionMap = new Map(document.modelProviders.map((entry) => [entry.name, entry]));
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const insertAt = findTopLevelInsertIndex(document.rawText);
|
|
436
|
-
const text = `profile = ${quoted}${document.lineEnding}`;
|
|
437
|
-
operations.push({
|
|
438
|
-
kind: "insert-at",
|
|
439
|
-
index: insertAt,
|
|
440
|
-
text,
|
|
441
|
-
});
|
|
442
|
-
}
|
|
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
|
+
});
|
|
443
435
|
}
|
|
444
|
-
for (const profileName of args.deleteProfiles ?? []) {
|
|
436
|
+
for (const profileName of [...(args.deleteProfiles ?? []), ...(args.deleteLegacyProfilesByName ?? [])]) {
|
|
445
437
|
const section = sectionMap.get(profileName);
|
|
446
438
|
if (!section) {
|
|
447
439
|
continue;
|
|
@@ -478,39 +470,47 @@ function planConfigMutation(document, args) {
|
|
|
478
470
|
for (const [profileName, fields] of Object.entries(args.upsertModelProviders ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
479
471
|
const section = modelProviderSectionMap.get(profileName);
|
|
480
472
|
if (!section) {
|
|
481
|
-
const
|
|
482
|
-
const providerName = fields.name?.trim() ?? "";
|
|
483
|
-
if (!baseUrl) {
|
|
484
|
-
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${profileName}" requires base_url.`, {
|
|
485
|
-
profile: profileName,
|
|
486
|
-
modelProvider: profileName,
|
|
487
|
-
missingFields: [
|
|
488
|
-
!baseUrl ? "base_url" : null,
|
|
489
|
-
].filter((value) => Boolean(value)),
|
|
490
|
-
});
|
|
491
|
-
}
|
|
473
|
+
const normalizedFields = normalizeManagedModelProviderFields(profileName, fields);
|
|
492
474
|
const prefix = document.rawText.length > 0 && !document.rawText.endsWith(document.lineEnding)
|
|
493
475
|
? document.lineEnding
|
|
494
476
|
: "";
|
|
495
|
-
const requiresOpenAiAuth = fields.requiresOpenAiAuth;
|
|
496
|
-
const wireApi = fields.wireApi?.trim() ?? "";
|
|
497
477
|
operations.push({
|
|
498
478
|
kind: "insert-at",
|
|
499
479
|
index: document.rawText.length,
|
|
500
480
|
text: `${prefix}[model_providers.${profileName}]${document.lineEnding}` +
|
|
501
|
-
`base_url = ${JSON.stringify(baseUrl)}${document.lineEnding}` +
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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}`,
|
|
505
485
|
});
|
|
506
486
|
createdModelProviderSections.push(profileName);
|
|
507
487
|
continue;
|
|
508
488
|
}
|
|
509
|
-
const sectionUpdated = planModelProviderFieldMutation(section, fields, operations);
|
|
489
|
+
const sectionUpdated = planModelProviderFieldMutation(document, section, normalizeManagedModelProviderFields(profileName, fields), operations);
|
|
510
490
|
if (sectionUpdated) {
|
|
511
491
|
updatedModelProviders.push(profileName);
|
|
512
492
|
}
|
|
513
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
|
+
}
|
|
514
514
|
return {
|
|
515
515
|
operations,
|
|
516
516
|
createdProfileSections,
|
|
@@ -518,7 +518,8 @@ function planConfigMutation(document, args) {
|
|
|
518
518
|
deletedProfileSections,
|
|
519
519
|
updatedProfiles,
|
|
520
520
|
updatedModelProviders,
|
|
521
|
-
switchedActiveProfile: Boolean(args.
|
|
521
|
+
switchedActiveProfile: Boolean(args.setCurrentModelProvider !== undefined && args.setCurrentModelProvider !== document.currentModelProvider) ||
|
|
522
|
+
Boolean(args.setLegacyProfile !== undefined && args.setLegacyProfile !== document.legacyProfile),
|
|
522
523
|
};
|
|
523
524
|
}
|
|
524
525
|
/**
|
|
@@ -540,6 +541,9 @@ function applyPatchOperations(rawText, operations) {
|
|
|
540
541
|
}
|
|
541
542
|
return nextText;
|
|
542
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Plans managed field updates for one legacy profile section.
|
|
546
|
+
*/
|
|
543
547
|
function planSectionFieldMutation(document, section, fields, operations) {
|
|
544
548
|
let updated = false;
|
|
545
549
|
const modelText = fields.model !== undefined ? JSON.stringify(fields.model) : null;
|
|
@@ -587,14 +591,14 @@ function planSectionFieldMutation(document, section, fields, operations) {
|
|
|
587
591
|
/**
|
|
588
592
|
* Plans managed field updates for one model_providers section.
|
|
589
593
|
*/
|
|
590
|
-
function planModelProviderFieldMutation(section, fields, operations) {
|
|
594
|
+
function planModelProviderFieldMutation(document, section, fields, operations) {
|
|
591
595
|
let updated = false;
|
|
592
|
-
const baseUrlText =
|
|
593
|
-
const nameText =
|
|
594
|
-
const requiresOpenAiAuthText =
|
|
595
|
-
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);
|
|
596
600
|
const inserts = [];
|
|
597
|
-
if (
|
|
601
|
+
if (section.baseUrlValueRange) {
|
|
598
602
|
if (section.baseUrl !== fields.baseUrl) {
|
|
599
603
|
operations.push({
|
|
600
604
|
kind: "replace-range",
|
|
@@ -605,11 +609,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
605
609
|
updated = true;
|
|
606
610
|
}
|
|
607
611
|
}
|
|
608
|
-
else
|
|
612
|
+
else {
|
|
609
613
|
inserts.push(`base_url = ${baseUrlText}`);
|
|
610
614
|
updated = true;
|
|
611
615
|
}
|
|
612
|
-
if (
|
|
616
|
+
if (section.nameValueRange) {
|
|
613
617
|
if (section.providerName !== fields.name) {
|
|
614
618
|
operations.push({
|
|
615
619
|
kind: "replace-range",
|
|
@@ -620,11 +624,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
620
624
|
updated = true;
|
|
621
625
|
}
|
|
622
626
|
}
|
|
623
|
-
else
|
|
627
|
+
else {
|
|
624
628
|
inserts.push(`name = ${nameText}`);
|
|
625
629
|
updated = true;
|
|
626
630
|
}
|
|
627
|
-
if (
|
|
631
|
+
if (section.requiresOpenAiAuthValueRange) {
|
|
628
632
|
if (section.requiresOpenAiAuth !== fields.requiresOpenAiAuth) {
|
|
629
633
|
operations.push({
|
|
630
634
|
kind: "replace-range",
|
|
@@ -635,11 +639,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
635
639
|
updated = true;
|
|
636
640
|
}
|
|
637
641
|
}
|
|
638
|
-
else
|
|
642
|
+
else {
|
|
639
643
|
inserts.push(`requires_openai_auth = ${requiresOpenAiAuthText}`);
|
|
640
644
|
updated = true;
|
|
641
645
|
}
|
|
642
|
-
if (
|
|
646
|
+
if (section.wireApiValueRange) {
|
|
643
647
|
if (section.wireApi !== fields.wireApi) {
|
|
644
648
|
operations.push({
|
|
645
649
|
kind: "replace-range",
|
|
@@ -650,7 +654,7 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
650
654
|
updated = true;
|
|
651
655
|
}
|
|
652
656
|
}
|
|
653
|
-
else
|
|
657
|
+
else {
|
|
654
658
|
inserts.push(`wire_api = ${wireApiText}`);
|
|
655
659
|
updated = true;
|
|
656
660
|
}
|
|
@@ -658,11 +662,59 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
658
662
|
operations.push({
|
|
659
663
|
kind: "insert-at",
|
|
660
664
|
index: section.managedFieldInsertIndex,
|
|
661
|
-
text: `${inserts.join(
|
|
665
|
+
text: `${inserts.join(document.lineEnding)}${document.lineEnding}`,
|
|
662
666
|
});
|
|
663
667
|
}
|
|
664
668
|
return updated;
|
|
665
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
|
+
}
|
|
666
718
|
function splitWithOffsets(value) {
|
|
667
719
|
if (value.length === 0) {
|
|
668
720
|
return [];
|
|
@@ -787,6 +839,19 @@ function expandDeletionEnd(rawText, sectionStart, sectionEnd) {
|
|
|
787
839
|
}
|
|
788
840
|
return end;
|
|
789
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
|
+
}
|
|
790
855
|
function escapeRegExp(value) {
|
|
791
856
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
792
857
|
}
|