@minniexcode/codex-switch 0.1.0 → 0.1.2
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 +215 -179
- package/README.md +224 -183
- package/dist/app/add-provider.js +16 -23
- package/dist/app/bridge.js +2 -1
- 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 +38 -6
- package/dist/cli/output.js +29 -19
- package/dist/commands/handlers.js +3 -2
- package/dist/commands/help.js +3 -3
- package/dist/commands/registry.js +29 -29
- package/dist/domain/config.js +293 -209
- package/dist/domain/providers.js +8 -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/runtime/copilot-adapter.js +326 -70
- package/dist/runtime/copilot-bridge-worker.js +27 -2
- package/dist/runtime/copilot-bridge.js +192 -10
- package/dist/runtime/copilot-cli.js +7 -0
- package/dist/runtime/copilot-installer.js +59 -1
- package/dist/runtime/copilot-sdk-loader.js +4 -1
- package/dist/storage/config-repo.js +6 -14
- package/docs/Design/codex-switch-v0.1.0-design.md +32 -152
- package/docs/Design/codex-switch-v0.1.1-design.md +22 -0
- package/docs/Design/codex-switch-v0.1.2-design.md +65 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +65 -217
- package/docs/PRD/codex-switch-prd-v0.1.1.md +26 -0
- package/docs/PRD/codex-switch-prd-v0.1.2.md +41 -0
- 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 +1 -1
- package/docs/cli-usage.md +290 -223
- package/docs/codex-switch-command-design.md +2 -2
- package/docs/codex-switch-product-overview.md +18 -13
- package/docs/codex-switch-product-research.md +2 -2
- package/docs/codex-switch-technical-architecture.md +84 -1115
- package/package.json +2 -2
- package/docs/Design/codex-switch-copilot-integration-design.md +0 -517
- package/docs/Design/codex-switch-v0.0.10-design.md +0 -669
- package/docs/Design/codex-switch-v0.0.11-design.md +0 -824
- package/docs/Design/codex-switch-v0.0.12-design.md +0 -343
- package/docs/Design/codex-switch-v0.0.4-design.md +0 -874
- package/docs/Design/codex-switch-v0.0.5-design.md +0 -932
- package/docs/Design/codex-switch-v0.0.6-design.md +0 -708
- package/docs/Design/codex-switch-v0.0.7-design.md +0 -862
- package/docs/Design/codex-switch-v0.0.8-design.md +0 -132
- package/docs/Design/codex-switch-v0.0.9-design.md +0 -182
- package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +0 -413
- package/docs/PRD/codex-switch-prd-v0.0.10.md +0 -406
- package/docs/PRD/codex-switch-prd-v0.0.11.md +0 -577
- package/docs/PRD/codex-switch-prd-v0.0.12.md +0 -279
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +0 -446
- package/docs/PRD/codex-switch-prd-v0.0.8.md +0 -62
- package/docs/PRD/codex-switch-prd-v0.0.9.md +0 -166
- package/docs/PRD/codex-switch-prd.md +0 -650
- package/docs/Tests/test-report-0.0.5.md +0 -163
- package/docs/Tests/test-report-0.0.7.md +0 -118
- package/docs/Tests/testing-bridge-v0.0.9.md +0 -367
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,16 @@ function parseStructuredConfig(configContent) {
|
|
|
126
133
|
requiresOpenAiAuth: null,
|
|
127
134
|
wireApiValueRange: null,
|
|
128
135
|
wireApi: null,
|
|
136
|
+
streamIdleTimeoutMsValueRange: null,
|
|
137
|
+
streamIdleTimeoutMs: null,
|
|
138
|
+
envKeyValueRange: null,
|
|
139
|
+
envKey: null,
|
|
140
|
+
envKeyInstructionsValueRange: null,
|
|
141
|
+
envKeyInstructions: null,
|
|
142
|
+
envKeyLineRange: null,
|
|
143
|
+
envKeyInstructionsLineRange: null,
|
|
129
144
|
};
|
|
130
|
-
modelProviders.push(
|
|
145
|
+
modelProviders.push(currentModelProviderSection);
|
|
131
146
|
inRoot = false;
|
|
132
147
|
continue;
|
|
133
148
|
}
|
|
@@ -136,81 +151,97 @@ function parseStructuredConfig(configContent) {
|
|
|
136
151
|
currentProfile.sectionEnd = line.start;
|
|
137
152
|
currentProfile = null;
|
|
138
153
|
}
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
154
|
+
if (currentModelProviderSection) {
|
|
155
|
+
currentModelProviderSection.sectionEnd = line.start;
|
|
156
|
+
currentModelProviderSection = null;
|
|
142
157
|
}
|
|
143
158
|
inRoot = false;
|
|
144
159
|
continue;
|
|
145
160
|
}
|
|
146
161
|
if (inRoot) {
|
|
162
|
+
const modelMatch = matchKeyValueLine(line.content, "model");
|
|
163
|
+
if (modelMatch && !currentModel) {
|
|
164
|
+
currentModel = modelMatch.value;
|
|
165
|
+
currentModelRange = toAbsoluteRange(line.start, modelMatch.valueStart, modelMatch.valueEnd);
|
|
166
|
+
currentModelLineRange = { start: line.start, end: line.end };
|
|
167
|
+
}
|
|
168
|
+
const modelProviderMatch = matchKeyValueLine(line.content, "model_provider");
|
|
169
|
+
if (modelProviderMatch && !currentModelProvider) {
|
|
170
|
+
currentModelProvider = modelProviderMatch.value;
|
|
171
|
+
currentModelProviderRange = toAbsoluteRange(line.start, modelProviderMatch.valueStart, modelProviderMatch.valueEnd);
|
|
172
|
+
currentModelProviderLineRange = { start: line.start, end: line.end };
|
|
173
|
+
}
|
|
147
174
|
const profileMatch = matchKeyValueLine(line.content, "profile");
|
|
148
|
-
if (profileMatch && !
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
end: line.start + profileMatch.valueEnd,
|
|
153
|
-
};
|
|
175
|
+
if (profileMatch && !legacyProfile) {
|
|
176
|
+
legacyProfile = profileMatch.value;
|
|
177
|
+
legacyProfileRange = toAbsoluteRange(line.start, profileMatch.valueStart, profileMatch.valueEnd);
|
|
178
|
+
legacyProfileLineRange = { start: line.start, end: line.end };
|
|
154
179
|
}
|
|
155
180
|
}
|
|
156
181
|
if (currentProfile) {
|
|
157
182
|
const modelMatch = matchKeyValueLine(line.content, "model");
|
|
158
183
|
if (modelMatch) {
|
|
159
184
|
currentProfile.model = modelMatch.value;
|
|
160
|
-
currentProfile.modelValueRange =
|
|
161
|
-
start: line.start + modelMatch.valueStart,
|
|
162
|
-
end: line.start + modelMatch.valueEnd,
|
|
163
|
-
};
|
|
185
|
+
currentProfile.modelValueRange = toAbsoluteRange(line.start, modelMatch.valueStart, modelMatch.valueEnd);
|
|
164
186
|
}
|
|
165
187
|
const modelProviderMatch = matchKeyValueLine(line.content, "model_provider");
|
|
166
188
|
if (modelProviderMatch) {
|
|
167
189
|
currentProfile.modelProvider = modelProviderMatch.value;
|
|
168
|
-
currentProfile.modelProviderValueRange =
|
|
169
|
-
start: line.start + modelProviderMatch.valueStart,
|
|
170
|
-
end: line.start + modelProviderMatch.valueEnd,
|
|
171
|
-
};
|
|
190
|
+
currentProfile.modelProviderValueRange = toAbsoluteRange(line.start, modelProviderMatch.valueStart, modelProviderMatch.valueEnd);
|
|
172
191
|
}
|
|
173
192
|
}
|
|
174
|
-
if (
|
|
193
|
+
if (currentModelProviderSection) {
|
|
175
194
|
const baseUrlMatch = matchKeyValueLine(line.content, "base_url");
|
|
176
195
|
if (baseUrlMatch) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
start: line.start + baseUrlMatch.valueStart,
|
|
180
|
-
end: line.start + baseUrlMatch.valueEnd,
|
|
181
|
-
};
|
|
196
|
+
currentModelProviderSection.baseUrl = baseUrlMatch.value;
|
|
197
|
+
currentModelProviderSection.baseUrlValueRange = toAbsoluteRange(line.start, baseUrlMatch.valueStart, baseUrlMatch.valueEnd);
|
|
182
198
|
}
|
|
183
199
|
const nameMatch = matchKeyValueLine(line.content, "name");
|
|
184
200
|
if (nameMatch) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
start: line.start + nameMatch.valueStart,
|
|
188
|
-
end: line.start + nameMatch.valueEnd,
|
|
189
|
-
};
|
|
201
|
+
currentModelProviderSection.providerName = nameMatch.value;
|
|
202
|
+
currentModelProviderSection.nameValueRange = toAbsoluteRange(line.start, nameMatch.valueStart, nameMatch.valueEnd);
|
|
190
203
|
}
|
|
191
204
|
const requiresOpenAiAuthMatch = matchBooleanKeyValueLine(line.content, "requires_openai_auth");
|
|
192
205
|
if (requiresOpenAiAuthMatch) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
start: line.start + requiresOpenAiAuthMatch.valueStart,
|
|
196
|
-
end: line.start + requiresOpenAiAuthMatch.valueEnd,
|
|
197
|
-
};
|
|
206
|
+
currentModelProviderSection.requiresOpenAiAuth = requiresOpenAiAuthMatch.value;
|
|
207
|
+
currentModelProviderSection.requiresOpenAiAuthValueRange = toAbsoluteRange(line.start, requiresOpenAiAuthMatch.valueStart, requiresOpenAiAuthMatch.valueEnd);
|
|
198
208
|
}
|
|
199
209
|
const wireApiMatch = matchKeyValueLine(line.content, "wire_api");
|
|
200
210
|
if (wireApiMatch) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
211
|
+
currentModelProviderSection.wireApi = wireApiMatch.value;
|
|
212
|
+
currentModelProviderSection.wireApiValueRange = toAbsoluteRange(line.start, wireApiMatch.valueStart, wireApiMatch.valueEnd);
|
|
213
|
+
}
|
|
214
|
+
const streamIdleTimeoutMsMatch = matchNumberKeyValueLine(line.content, "stream_idle_timeout_ms");
|
|
215
|
+
if (streamIdleTimeoutMsMatch) {
|
|
216
|
+
currentModelProviderSection.streamIdleTimeoutMs = streamIdleTimeoutMsMatch.value;
|
|
217
|
+
currentModelProviderSection.streamIdleTimeoutMsValueRange = toAbsoluteRange(line.start, streamIdleTimeoutMsMatch.valueStart, streamIdleTimeoutMsMatch.valueEnd);
|
|
218
|
+
}
|
|
219
|
+
const envKeyMatch = matchKeyValueLine(line.content, "env_key");
|
|
220
|
+
if (envKeyMatch) {
|
|
221
|
+
currentModelProviderSection.envKey = envKeyMatch.value;
|
|
222
|
+
currentModelProviderSection.envKeyValueRange = toAbsoluteRange(line.start, envKeyMatch.valueStart, envKeyMatch.valueEnd);
|
|
223
|
+
currentModelProviderSection.envKeyLineRange = { start: line.start, end: line.end };
|
|
224
|
+
}
|
|
225
|
+
const envKeyInstructionsMatch = matchKeyValueLine(line.content, "env_key_instructions");
|
|
226
|
+
if (envKeyInstructionsMatch) {
|
|
227
|
+
currentModelProviderSection.envKeyInstructions = envKeyInstructionsMatch.value;
|
|
228
|
+
currentModelProviderSection.envKeyInstructionsValueRange = toAbsoluteRange(line.start, envKeyInstructionsMatch.valueStart, envKeyInstructionsMatch.valueEnd);
|
|
229
|
+
currentModelProviderSection.envKeyInstructionsLineRange = { start: line.start, end: line.end };
|
|
206
230
|
}
|
|
207
231
|
}
|
|
208
232
|
}
|
|
209
233
|
return {
|
|
210
234
|
rawText: configContent,
|
|
211
235
|
lineEnding,
|
|
212
|
-
|
|
213
|
-
|
|
236
|
+
currentModel,
|
|
237
|
+
currentModelRange,
|
|
238
|
+
currentModelLineRange,
|
|
239
|
+
currentModelProvider,
|
|
240
|
+
currentModelProviderRange,
|
|
241
|
+
currentModelProviderLineRange,
|
|
242
|
+
legacyProfile,
|
|
243
|
+
legacyProfileRange,
|
|
244
|
+
legacyProfileLineRange,
|
|
214
245
|
profiles: profiles.map((profile) => ({
|
|
215
246
|
...profile,
|
|
216
247
|
managedFieldInsertIndex: findManagedFieldInsertIndex(configContent, profile.sectionStart, profile.sectionEnd),
|
|
@@ -222,7 +253,7 @@ function parseStructuredConfig(configContent) {
|
|
|
222
253
|
};
|
|
223
254
|
}
|
|
224
255
|
/**
|
|
225
|
-
* Builds the
|
|
256
|
+
* Builds the legacy profile inspection views used by config commands and diagnostics.
|
|
226
257
|
*/
|
|
227
258
|
function buildManagedProfileViews(document, providers) {
|
|
228
259
|
const linkMap = buildProfileLinkMap(providers);
|
|
@@ -236,7 +267,7 @@ function buildManagedProfileViews(document, providers) {
|
|
|
236
267
|
views.push({
|
|
237
268
|
name: section.name,
|
|
238
269
|
managed: linkInfo.managed,
|
|
239
|
-
isActive: document.
|
|
270
|
+
isActive: document.currentModelProvider === section.name,
|
|
240
271
|
linkedProviders: [...linkInfo.linkedProviders].sort(),
|
|
241
272
|
model: section.model,
|
|
242
273
|
modelProvider: section.modelProvider,
|
|
@@ -252,7 +283,7 @@ function buildManagedProfileViews(document, providers) {
|
|
|
252
283
|
views.push({
|
|
253
284
|
name: profile,
|
|
254
285
|
managed: true,
|
|
255
|
-
isActive: document.
|
|
286
|
+
isActive: document.currentModelProvider === profile,
|
|
256
287
|
linkedProviders: [...linkInfo.linkedProviders].sort(),
|
|
257
288
|
model: null,
|
|
258
289
|
modelProvider: null,
|
|
@@ -269,109 +300,60 @@ function buildManagedProfileViews(document, providers) {
|
|
|
269
300
|
function collectConfigConsistencyIssues(document, providers) {
|
|
270
301
|
const issues = [];
|
|
271
302
|
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
|
-
|
|
303
|
+
const activeModelProvider = document.currentModelProvider;
|
|
304
|
+
const activeProviderSection = activeModelProvider
|
|
305
|
+
? document.modelProviders.find((entry) => entry.name === activeModelProvider) ?? null
|
|
306
|
+
: null;
|
|
307
|
+
if (!document.currentModel) {
|
|
308
|
+
issues.push({ code: "MODEL_MISSING", modelProvider: activeModelProvider ?? "(none)" });
|
|
309
|
+
}
|
|
310
|
+
if (!document.currentModelProvider) {
|
|
311
|
+
issues.push({ code: "MODEL_PROVIDER_MISSING" });
|
|
312
|
+
}
|
|
313
|
+
if (document.legacyProfile) {
|
|
314
|
+
issues.push({ code: "LEGACY_PROFILE_SELECTOR", profile: document.legacyProfile });
|
|
315
|
+
}
|
|
316
|
+
for (const profile of document.profiles) {
|
|
317
|
+
issues.push({ code: "LEGACY_PROFILE_SECTION", profile: profile.name });
|
|
318
|
+
}
|
|
319
|
+
if (activeModelProvider && !activeProviderSection) {
|
|
320
|
+
issues.push({ code: "MODEL_PROVIDER_SECTION_MISSING", modelProvider: activeModelProvider });
|
|
321
|
+
}
|
|
322
|
+
if (activeModelProvider && activeProviderSection && !activeProviderSection.baseUrl) {
|
|
323
|
+
issues.push({ code: "MODEL_PROVIDER_BASE_URL_MISSING", modelProvider: activeModelProvider });
|
|
324
|
+
}
|
|
325
|
+
if (activeProviderSection?.envKey) {
|
|
326
|
+
issues.push({
|
|
327
|
+
code: "LEGACY_MODEL_PROVIDER_ENV_KEY",
|
|
328
|
+
modelProvider: activeProviderSection.name,
|
|
329
|
+
envKey: activeProviderSection.envKey,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (activeModelProvider && providerMap) {
|
|
333
|
+
const linkedProviders = Object.entries(providerMap)
|
|
334
|
+
.filter(([, provider]) => provider.profile === activeModelProvider)
|
|
335
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
336
|
+
if (linkedProviders.length === 1 && activeProviderSection?.baseUrl) {
|
|
337
|
+
const [providerName, provider] = linkedProviders[0];
|
|
338
|
+
if (!provider.runtime &&
|
|
339
|
+
typeof provider.baseUrl === "string" &&
|
|
340
|
+
provider.baseUrl.trim() !== "" &&
|
|
341
|
+
provider.baseUrl !== activeProviderSection.baseUrl) {
|
|
296
342
|
issues.push({
|
|
297
|
-
code: "
|
|
298
|
-
|
|
343
|
+
code: "PROVIDER_BASE_URL_MISMATCH",
|
|
344
|
+
modelProvider: activeModelProvider,
|
|
345
|
+
provider: providerName,
|
|
346
|
+
providerBaseUrl: provider.baseUrl,
|
|
347
|
+
configBaseUrl: activeProviderSection.baseUrl,
|
|
348
|
+
providerType: "direct",
|
|
299
349
|
});
|
|
300
350
|
}
|
|
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
351
|
}
|
|
365
352
|
}
|
|
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
|
-
});
|
|
353
|
+
return issues.sort((left, right) => left.code.localeCompare(right.code));
|
|
372
354
|
}
|
|
373
355
|
/**
|
|
374
|
-
* Ensures the minimal managed profile fields are available before a new section is created.
|
|
356
|
+
* Ensures the minimal managed profile fields are available before a new legacy section is created.
|
|
375
357
|
*/
|
|
376
358
|
function validateManagedProfileCreation(profile, fields) {
|
|
377
359
|
const model = fields.model?.trim() ?? "";
|
|
@@ -391,7 +373,7 @@ function validateManagedProfileCreation(profile, fields) {
|
|
|
391
373
|
};
|
|
392
374
|
}
|
|
393
375
|
/**
|
|
394
|
-
* Computes keep/delete/switch outcomes when a provider leaves or changes
|
|
376
|
+
* Computes keep/delete/switch outcomes when a provider leaves or changes model-provider bindings.
|
|
395
377
|
*/
|
|
396
378
|
function planProfileLifecycleOutcome(args) {
|
|
397
379
|
if (!args.oldProfile || args.oldProfile === args.newProfile) {
|
|
@@ -435,7 +417,7 @@ function planProfileLifecycleOutcome(args) {
|
|
|
435
417
|
};
|
|
436
418
|
}
|
|
437
419
|
/**
|
|
438
|
-
* Builds a text patch plan for
|
|
420
|
+
* Builds a text patch plan for route fields, legacy selectors, and provider-section mutations.
|
|
439
421
|
*/
|
|
440
422
|
function planConfigMutation(document, args) {
|
|
441
423
|
const operations = [];
|
|
@@ -446,27 +428,19 @@ function planConfigMutation(document, args) {
|
|
|
446
428
|
const updatedModelProviders = [];
|
|
447
429
|
const sectionMap = new Map(document.profiles.map((profile) => [profile.name, profile]));
|
|
448
430
|
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
|
-
}
|
|
431
|
+
planRootFieldMutation(document, "model", document.currentModel, document.currentModelRange, document.currentModelLineRange, args.setCurrentModel, operations);
|
|
432
|
+
planRootFieldMutation(document, "model_provider", document.currentModelProvider, document.currentModelProviderRange, document.currentModelProviderLineRange, args.setCurrentModelProvider, operations);
|
|
433
|
+
if (args.setLegacyProfile !== undefined) {
|
|
434
|
+
planRootFieldMutation(document, "profile", document.legacyProfile, document.legacyProfileRange, document.legacyProfileLineRange, args.setLegacyProfile, operations);
|
|
435
|
+
}
|
|
436
|
+
if (args.deleteLegacyProfile && document.legacyProfileLineRange) {
|
|
437
|
+
operations.push({
|
|
438
|
+
kind: "delete-range",
|
|
439
|
+
start: document.legacyProfileLineRange.start,
|
|
440
|
+
end: expandLineDeletionStart(document.rawText, document.legacyProfileLineRange.start, document.legacyProfileLineRange.end),
|
|
441
|
+
});
|
|
468
442
|
}
|
|
469
|
-
for (const profileName of args.deleteProfiles ?? []) {
|
|
443
|
+
for (const profileName of [...(args.deleteProfiles ?? []), ...(args.deleteLegacyProfilesByName ?? [])]) {
|
|
470
444
|
const section = sectionMap.get(profileName);
|
|
471
445
|
if (!section) {
|
|
472
446
|
continue;
|
|
@@ -503,39 +477,50 @@ function planConfigMutation(document, args) {
|
|
|
503
477
|
for (const [profileName, fields] of Object.entries(args.upsertModelProviders ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
504
478
|
const section = modelProviderSectionMap.get(profileName);
|
|
505
479
|
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
|
-
}
|
|
480
|
+
const normalizedFields = normalizeManagedModelProviderFields(profileName, fields);
|
|
517
481
|
const prefix = document.rawText.length > 0 && !document.rawText.endsWith(document.lineEnding)
|
|
518
482
|
? document.lineEnding
|
|
519
483
|
: "";
|
|
520
|
-
const requiresOpenAiAuth = fields.requiresOpenAiAuth;
|
|
521
|
-
const wireApi = fields.wireApi?.trim() ?? "";
|
|
522
484
|
operations.push({
|
|
523
485
|
kind: "insert-at",
|
|
524
486
|
index: document.rawText.length,
|
|
525
487
|
text: `${prefix}[model_providers.${profileName}]${document.lineEnding}` +
|
|
526
|
-
`base_url = ${JSON.stringify(baseUrl)}${document.lineEnding}` +
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
488
|
+
`base_url = ${JSON.stringify(normalizedFields.baseUrl)}${document.lineEnding}` +
|
|
489
|
+
`name = ${JSON.stringify(normalizedFields.name)}${document.lineEnding}` +
|
|
490
|
+
`requires_openai_auth = ${String(normalizedFields.requiresOpenAiAuth)}${document.lineEnding}` +
|
|
491
|
+
`wire_api = ${JSON.stringify(normalizedFields.wireApi)}${document.lineEnding}` +
|
|
492
|
+
(normalizedFields.streamIdleTimeoutMs !== undefined
|
|
493
|
+
? `stream_idle_timeout_ms = ${String(normalizedFields.streamIdleTimeoutMs)}${document.lineEnding}`
|
|
494
|
+
: ""),
|
|
530
495
|
});
|
|
531
496
|
createdModelProviderSections.push(profileName);
|
|
532
497
|
continue;
|
|
533
498
|
}
|
|
534
|
-
const sectionUpdated = planModelProviderFieldMutation(section, fields, operations);
|
|
499
|
+
const sectionUpdated = planModelProviderFieldMutation(document, section, normalizeManagedModelProviderFields(profileName, fields), operations);
|
|
535
500
|
if (sectionUpdated) {
|
|
536
501
|
updatedModelProviders.push(profileName);
|
|
537
502
|
}
|
|
538
503
|
}
|
|
504
|
+
for (const profileName of args.scrubModelProviderEnvKeys ?? []) {
|
|
505
|
+
const section = modelProviderSectionMap.get(profileName);
|
|
506
|
+
if (!section) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (section.envKeyLineRange) {
|
|
510
|
+
operations.push({
|
|
511
|
+
kind: "delete-range",
|
|
512
|
+
start: section.envKeyLineRange.start,
|
|
513
|
+
end: expandLineDeletionStart(document.rawText, section.envKeyLineRange.start, section.envKeyLineRange.end),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
if (section.envKeyInstructionsLineRange) {
|
|
517
|
+
operations.push({
|
|
518
|
+
kind: "delete-range",
|
|
519
|
+
start: section.envKeyInstructionsLineRange.start,
|
|
520
|
+
end: expandLineDeletionStart(document.rawText, section.envKeyInstructionsLineRange.start, section.envKeyInstructionsLineRange.end),
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
539
524
|
return {
|
|
540
525
|
operations,
|
|
541
526
|
createdProfileSections,
|
|
@@ -543,7 +528,8 @@ function planConfigMutation(document, args) {
|
|
|
543
528
|
deletedProfileSections,
|
|
544
529
|
updatedProfiles,
|
|
545
530
|
updatedModelProviders,
|
|
546
|
-
switchedActiveProfile: Boolean(args.
|
|
531
|
+
switchedActiveProfile: Boolean(args.setCurrentModelProvider !== undefined && args.setCurrentModelProvider !== document.currentModelProvider) ||
|
|
532
|
+
Boolean(args.setLegacyProfile !== undefined && args.setLegacyProfile !== document.legacyProfile),
|
|
547
533
|
};
|
|
548
534
|
}
|
|
549
535
|
/**
|
|
@@ -565,6 +551,9 @@ function applyPatchOperations(rawText, operations) {
|
|
|
565
551
|
}
|
|
566
552
|
return nextText;
|
|
567
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Plans managed field updates for one legacy profile section.
|
|
556
|
+
*/
|
|
568
557
|
function planSectionFieldMutation(document, section, fields, operations) {
|
|
569
558
|
let updated = false;
|
|
570
559
|
const modelText = fields.model !== undefined ? JSON.stringify(fields.model) : null;
|
|
@@ -612,14 +601,15 @@ function planSectionFieldMutation(document, section, fields, operations) {
|
|
|
612
601
|
/**
|
|
613
602
|
* Plans managed field updates for one model_providers section.
|
|
614
603
|
*/
|
|
615
|
-
function planModelProviderFieldMutation(section, fields, operations) {
|
|
604
|
+
function planModelProviderFieldMutation(document, section, fields, operations) {
|
|
616
605
|
let updated = false;
|
|
617
|
-
const baseUrlText =
|
|
618
|
-
const nameText =
|
|
619
|
-
const requiresOpenAiAuthText =
|
|
620
|
-
const wireApiText =
|
|
606
|
+
const baseUrlText = JSON.stringify(fields.baseUrl);
|
|
607
|
+
const nameText = JSON.stringify(fields.name);
|
|
608
|
+
const requiresOpenAiAuthText = String(fields.requiresOpenAiAuth);
|
|
609
|
+
const wireApiText = JSON.stringify(fields.wireApi);
|
|
610
|
+
const streamIdleTimeoutMsText = fields.streamIdleTimeoutMs !== undefined ? String(fields.streamIdleTimeoutMs) : null;
|
|
621
611
|
const inserts = [];
|
|
622
|
-
if (
|
|
612
|
+
if (section.baseUrlValueRange) {
|
|
623
613
|
if (section.baseUrl !== fields.baseUrl) {
|
|
624
614
|
operations.push({
|
|
625
615
|
kind: "replace-range",
|
|
@@ -630,11 +620,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
630
620
|
updated = true;
|
|
631
621
|
}
|
|
632
622
|
}
|
|
633
|
-
else
|
|
623
|
+
else {
|
|
634
624
|
inserts.push(`base_url = ${baseUrlText}`);
|
|
635
625
|
updated = true;
|
|
636
626
|
}
|
|
637
|
-
if (
|
|
627
|
+
if (section.nameValueRange) {
|
|
638
628
|
if (section.providerName !== fields.name) {
|
|
639
629
|
operations.push({
|
|
640
630
|
kind: "replace-range",
|
|
@@ -645,11 +635,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
645
635
|
updated = true;
|
|
646
636
|
}
|
|
647
637
|
}
|
|
648
|
-
else
|
|
638
|
+
else {
|
|
649
639
|
inserts.push(`name = ${nameText}`);
|
|
650
640
|
updated = true;
|
|
651
641
|
}
|
|
652
|
-
if (
|
|
642
|
+
if (section.requiresOpenAiAuthValueRange) {
|
|
653
643
|
if (section.requiresOpenAiAuth !== fields.requiresOpenAiAuth) {
|
|
654
644
|
operations.push({
|
|
655
645
|
kind: "replace-range",
|
|
@@ -660,11 +650,11 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
660
650
|
updated = true;
|
|
661
651
|
}
|
|
662
652
|
}
|
|
663
|
-
else
|
|
653
|
+
else {
|
|
664
654
|
inserts.push(`requires_openai_auth = ${requiresOpenAiAuthText}`);
|
|
665
655
|
updated = true;
|
|
666
656
|
}
|
|
667
|
-
if (
|
|
657
|
+
if (section.wireApiValueRange) {
|
|
668
658
|
if (section.wireApi !== fields.wireApi) {
|
|
669
659
|
operations.push({
|
|
670
660
|
kind: "replace-range",
|
|
@@ -675,19 +665,85 @@ function planModelProviderFieldMutation(section, fields, operations) {
|
|
|
675
665
|
updated = true;
|
|
676
666
|
}
|
|
677
667
|
}
|
|
678
|
-
else
|
|
668
|
+
else {
|
|
679
669
|
inserts.push(`wire_api = ${wireApiText}`);
|
|
680
670
|
updated = true;
|
|
681
671
|
}
|
|
672
|
+
if (streamIdleTimeoutMsText !== null) {
|
|
673
|
+
if (section.streamIdleTimeoutMsValueRange) {
|
|
674
|
+
if (section.streamIdleTimeoutMs !== fields.streamIdleTimeoutMs) {
|
|
675
|
+
operations.push({
|
|
676
|
+
kind: "replace-range",
|
|
677
|
+
start: section.streamIdleTimeoutMsValueRange.start,
|
|
678
|
+
end: section.streamIdleTimeoutMsValueRange.end,
|
|
679
|
+
text: streamIdleTimeoutMsText,
|
|
680
|
+
});
|
|
681
|
+
updated = true;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
inserts.push(`stream_idle_timeout_ms = ${streamIdleTimeoutMsText}`);
|
|
686
|
+
updated = true;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
682
689
|
if (inserts.length > 0) {
|
|
683
690
|
operations.push({
|
|
684
691
|
kind: "insert-at",
|
|
685
692
|
index: section.managedFieldInsertIndex,
|
|
686
|
-
text: `${inserts.join(
|
|
693
|
+
text: `${inserts.join(document.lineEnding)}${document.lineEnding}`,
|
|
687
694
|
});
|
|
688
695
|
}
|
|
689
696
|
return updated;
|
|
690
697
|
}
|
|
698
|
+
function planRootFieldMutation(document, key, currentValue, currentValueRange, currentLineRange, nextValue, operations) {
|
|
699
|
+
if (nextValue === undefined) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (nextValue === null) {
|
|
703
|
+
if (currentLineRange) {
|
|
704
|
+
operations.push({
|
|
705
|
+
kind: "delete-range",
|
|
706
|
+
start: currentLineRange.start,
|
|
707
|
+
end: expandLineDeletionStart(document.rawText, currentLineRange.start, currentLineRange.end),
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (currentValueRange) {
|
|
713
|
+
if (currentValue !== nextValue) {
|
|
714
|
+
operations.push({
|
|
715
|
+
kind: "replace-range",
|
|
716
|
+
start: currentValueRange.start,
|
|
717
|
+
end: currentValueRange.end,
|
|
718
|
+
text: JSON.stringify(nextValue),
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const insertAt = findTopLevelInsertIndex(document.rawText);
|
|
724
|
+
operations.push({
|
|
725
|
+
kind: "insert-at",
|
|
726
|
+
index: insertAt,
|
|
727
|
+
text: `${key} = ${JSON.stringify(nextValue)}${document.lineEnding}`,
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
function normalizeManagedModelProviderFields(profileName, fields) {
|
|
731
|
+
const baseUrl = fields.baseUrl?.trim() ?? "";
|
|
732
|
+
if (!baseUrl) {
|
|
733
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${profileName}" requires base_url.`, {
|
|
734
|
+
profile: profileName,
|
|
735
|
+
modelProvider: profileName,
|
|
736
|
+
missingFields: ["base_url"],
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
baseUrl,
|
|
741
|
+
name: fields.name?.trim() || profileName,
|
|
742
|
+
requiresOpenAiAuth: fields.requiresOpenAiAuth ?? true,
|
|
743
|
+
wireApi: fields.wireApi?.trim() || "responses",
|
|
744
|
+
streamIdleTimeoutMs: fields.streamIdleTimeoutMs,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
691
747
|
function splitWithOffsets(value) {
|
|
692
748
|
if (value.length === 0) {
|
|
693
749
|
return [];
|
|
@@ -746,6 +802,21 @@ function matchBooleanKeyValueLine(line, key) {
|
|
|
746
802
|
valueEnd,
|
|
747
803
|
};
|
|
748
804
|
}
|
|
805
|
+
function matchNumberKeyValueLine(line, key) {
|
|
806
|
+
const match = line.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(\\d+)\\s*(#.*)?$`));
|
|
807
|
+
if (!match || match.index === undefined) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
const valueStart = line.indexOf(match[1], match.index);
|
|
811
|
+
if (valueStart === -1) {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
value: Number(match[1]),
|
|
816
|
+
valueStart,
|
|
817
|
+
valueEnd: valueStart + match[1].length,
|
|
818
|
+
};
|
|
819
|
+
}
|
|
749
820
|
function findManagedFieldInsertIndex(rawText, sectionStart, sectionEnd) {
|
|
750
821
|
const sectionText = rawText.slice(sectionStart, sectionEnd);
|
|
751
822
|
const lines = splitWithOffsets(sectionText);
|
|
@@ -812,6 +883,19 @@ function expandDeletionEnd(rawText, sectionStart, sectionEnd) {
|
|
|
812
883
|
}
|
|
813
884
|
return end;
|
|
814
885
|
}
|
|
886
|
+
function expandLineDeletionStart(rawText, start, end) {
|
|
887
|
+
let nextEnd = end;
|
|
888
|
+
while (nextEnd < rawText.length && (rawText[nextEnd] === "\r" || rawText[nextEnd] === "\n")) {
|
|
889
|
+
nextEnd += 1;
|
|
890
|
+
}
|
|
891
|
+
return nextEnd;
|
|
892
|
+
}
|
|
893
|
+
function toAbsoluteRange(lineStart, valueStart, valueEnd) {
|
|
894
|
+
return {
|
|
895
|
+
start: lineStart + valueStart,
|
|
896
|
+
end: lineStart + valueEnd,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
815
899
|
function escapeRegExp(value) {
|
|
816
900
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
817
901
|
}
|