@hifullmoon/aicommit 1.5.1 → 2.0.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/.aicommit.config.example.json +30 -10
- package/CHANGELOG.md +24 -1
- package/README.md +43 -41
- package/README.zh-CN.md +68 -66
- package/SECURITY.md +1 -1
- package/docs/distribution.md +3 -38
- package/docs/privacy.md +8 -8
- package/docs/provider-presets.md +22 -9
- package/docs/troubleshooting.md +0 -1
- package/package.json +2 -3
- package/presets/provider-presets.json +48 -15
- package/schemas/aicommit-provider-presets.schema.json +24 -4
- package/src/cli.js +38 -7
- package/src/completion.js +4 -1
- package/src/config-command.js +7 -5
- package/src/config.js +198 -26
- package/src/doctor.js +5 -3
- package/src/main.js +29 -6
- package/src/provider-presets.js +43 -15
- package/src/setup.js +66 -37
- package/src/ui.js +66 -13
package/src/config.js
CHANGED
|
@@ -25,10 +25,13 @@ import { isExtensionProviderType, validateExtensionsConfig } from './extensions.
|
|
|
25
25
|
// global config. Project config may tune generation/display behaviour, but all
|
|
26
26
|
// connection and provider selection fields remain user-owned.
|
|
27
27
|
export const PROJECT_CONNECTION_KEYS = new Set([
|
|
28
|
+
'schemaVersion',
|
|
28
29
|
'apiUrl',
|
|
29
30
|
'apiKey',
|
|
30
31
|
'apiKeyEnv',
|
|
31
32
|
'modelId',
|
|
33
|
+
'models',
|
|
34
|
+
'defaultModel',
|
|
32
35
|
'providerType',
|
|
33
36
|
'providers',
|
|
34
37
|
'defaultProvider',
|
|
@@ -193,6 +196,59 @@ export const DEFAULT_CONFIG = {
|
|
|
193
196
|
prompt: '',
|
|
194
197
|
};
|
|
195
198
|
|
|
199
|
+
export const CONFIG_SCHEMA_VERSION = 1;
|
|
200
|
+
|
|
201
|
+
const CONFIG_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
202
|
+
const TOP_LEVEL_CONNECTION_KEYS = new Set([
|
|
203
|
+
'apiUrl',
|
|
204
|
+
'apiKey',
|
|
205
|
+
'apiKeyEnv',
|
|
206
|
+
'modelId',
|
|
207
|
+
'providerType',
|
|
208
|
+
'defaultModel',
|
|
209
|
+
'models',
|
|
210
|
+
'extraBody',
|
|
211
|
+
]);
|
|
212
|
+
const USER_CONFIG_KEYS = new Set([
|
|
213
|
+
'schemaVersion',
|
|
214
|
+
'defaultProvider',
|
|
215
|
+
'providers',
|
|
216
|
+
...Object.keys(DEFAULT_CONFIG).filter((key) => !TOP_LEVEL_CONNECTION_KEYS.has(key)),
|
|
217
|
+
]);
|
|
218
|
+
const PROVIDER_CONFIG_KEYS = new Set([
|
|
219
|
+
'providerType',
|
|
220
|
+
'apiUrl',
|
|
221
|
+
'apiKey',
|
|
222
|
+
'apiKeyEnv',
|
|
223
|
+
'credentialHelper',
|
|
224
|
+
'retry',
|
|
225
|
+
'defaultModel',
|
|
226
|
+
'models',
|
|
227
|
+
]);
|
|
228
|
+
const MODEL_CONFIG_KEYS = new Set([
|
|
229
|
+
'label',
|
|
230
|
+
'modelId',
|
|
231
|
+
'temperature',
|
|
232
|
+
'maxTokens',
|
|
233
|
+
'timeoutMs',
|
|
234
|
+
'reasoning',
|
|
235
|
+
'extraBody',
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
const DEFAULT_PROVIDER_CATALOG = Object.freeze({
|
|
239
|
+
defaultProvider: 'openai',
|
|
240
|
+
providers: {
|
|
241
|
+
openai: {
|
|
242
|
+
providerType: 'openai',
|
|
243
|
+
apiUrl: DEFAULT_CONFIG.apiUrl,
|
|
244
|
+
defaultModel: 'default',
|
|
245
|
+
models: {
|
|
246
|
+
default: { modelId: DEFAULT_CONFIG.modelId },
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
196
252
|
function mergeConfig(base, override) {
|
|
197
253
|
const merged = deepMerge(base, override);
|
|
198
254
|
if (Object.hasOwn(override, 'commitPolicy')) {
|
|
@@ -207,39 +263,139 @@ function mergeConfig(base, override) {
|
|
|
207
263
|
return merged;
|
|
208
264
|
}
|
|
209
265
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// the providers/defaultProvider keys so downstream code sees a plain flat
|
|
214
|
-
// config.
|
|
215
|
-
function resolveProvider(config, cliProvider) {
|
|
216
|
-
const providers = config.providers;
|
|
266
|
+
function object(value) {
|
|
267
|
+
return value && typeof value === 'object' && !Array.isArray(value);
|
|
268
|
+
}
|
|
217
269
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
270
|
+
function assertKnownKeys(value, allowed, path) {
|
|
271
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
272
|
+
if (unknown.length) {
|
|
273
|
+
throw new Error(`Invalid config "${path}": unknown properties: ${unknown.join(', ')}.`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function assertConfigId(value, path) {
|
|
278
|
+
if (typeof value !== 'string' || !CONFIG_ID_RE.test(value)) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`Invalid config "${path}": expected 1-64 letters, digits, dots, dashes, or underscores.`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function providerRuntimeConfig(provider) {
|
|
286
|
+
const { defaultModel: _defaultModel, models: _models, ...runtime } = provider;
|
|
287
|
+
return runtime;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function modelRuntimeConfig(model) {
|
|
291
|
+
const { label: _label, ...runtime } = model;
|
|
292
|
+
return runtime;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function validateUserConfig(value) {
|
|
296
|
+
if (!object(value)) throw new Error('Invalid user config: expected a JSON object.');
|
|
297
|
+
if (value.schemaVersion !== CONFIG_SCHEMA_VERSION) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`Invalid config "schemaVersion": expected ${CONFIG_SCHEMA_VERSION}. ` +
|
|
300
|
+
'Run "aicommit setup" to create the current Provider/Model format.',
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
assertKnownKeys(value, USER_CONFIG_KEYS, 'root');
|
|
304
|
+
assertConfigId(value.defaultProvider, 'defaultProvider');
|
|
305
|
+
if (!object(value.providers) || Object.keys(value.providers).length === 0) {
|
|
306
|
+
throw new Error('Invalid config "providers": expected a non-empty object.');
|
|
307
|
+
}
|
|
308
|
+
if (!Object.hasOwn(value.providers, value.defaultProvider)) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`Invalid config "defaultProvider": unknown provider "${value.defaultProvider}".`,
|
|
223
311
|
);
|
|
224
312
|
}
|
|
225
313
|
|
|
226
|
-
|
|
227
|
-
|
|
314
|
+
const globalConfig = { ...value };
|
|
315
|
+
delete globalConfig.schemaVersion;
|
|
316
|
+
delete globalConfig.defaultProvider;
|
|
317
|
+
delete globalConfig.providers;
|
|
318
|
+
const baseConfig = mergeConfig(DEFAULT_CONFIG, globalConfig);
|
|
319
|
+
|
|
320
|
+
for (const [providerName, provider] of Object.entries(value.providers)) {
|
|
321
|
+
assertConfigId(providerName, `providers.${providerName}`);
|
|
322
|
+
if (!object(provider)) {
|
|
323
|
+
throw new Error(`Invalid config "providers.${providerName}": expected an object.`);
|
|
324
|
+
}
|
|
325
|
+
assertKnownKeys(provider, PROVIDER_CONFIG_KEYS, `providers.${providerName}`);
|
|
326
|
+
for (const required of ['providerType', 'apiUrl', 'defaultModel', 'models']) {
|
|
327
|
+
if (!Object.hasOwn(provider, required)) {
|
|
328
|
+
throw new Error(`Invalid config "providers.${providerName}": missing ${required}.`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
assertConfigId(provider.defaultModel, `providers.${providerName}.defaultModel`);
|
|
332
|
+
if (!object(provider.models) || Object.keys(provider.models).length === 0) {
|
|
333
|
+
throw new Error(`Invalid config "providers.${providerName}.models": expected models.`);
|
|
334
|
+
}
|
|
335
|
+
if (!Object.hasOwn(provider.models, provider.defaultModel)) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`Invalid config "providers.${providerName}.defaultModel": unknown model ` +
|
|
338
|
+
`"${provider.defaultModel}".`,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const providerConfig = mergeConfig(baseConfig, providerRuntimeConfig(provider));
|
|
343
|
+
for (const [modelName, model] of Object.entries(provider.models)) {
|
|
344
|
+
assertConfigId(modelName, `providers.${providerName}.models.${modelName}`);
|
|
345
|
+
if (!object(model)) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`Invalid config "providers.${providerName}.models.${modelName}": expected an object.`,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
assertKnownKeys(model, MODEL_CONFIG_KEYS, `providers.${providerName}.models.${modelName}`);
|
|
351
|
+
if (!Object.hasOwn(model, 'modelId')) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`Invalid config "providers.${providerName}.models.${modelName}": missing modelId.`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (
|
|
357
|
+
Object.hasOwn(model, 'label') &&
|
|
358
|
+
(typeof model.label !== 'string' || !model.label.trim() || model.label.length > 80)
|
|
359
|
+
) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Invalid config "providers.${providerName}.models.${modelName}.label": ` +
|
|
362
|
+
'expected a non-empty string of at most 80 characters.',
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
validateConfig(mergeConfig(providerConfig, modelRuntimeConfig(model)));
|
|
366
|
+
}
|
|
228
367
|
}
|
|
229
368
|
|
|
230
|
-
|
|
369
|
+
return value;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Select a Provider and one of its named Model profiles, then flatten both
|
|
373
|
+
// layers so request, split, and extension code keep consuming one runtime
|
|
374
|
+
// config with a concrete modelId.
|
|
375
|
+
function resolveSelection(config, catalog, cliProvider, cliModel) {
|
|
376
|
+
const providers = catalog.providers;
|
|
377
|
+
const providerName = cliProvider || catalog.defaultProvider;
|
|
378
|
+
const provider = providers[providerName];
|
|
231
379
|
|
|
232
|
-
if (!
|
|
380
|
+
if (!provider) {
|
|
381
|
+
throw fail(
|
|
382
|
+
ERROR_CATEGORIES.CONFIG,
|
|
383
|
+
`Unknown provider: "${providerName}". Available providers: ${Object.keys(providers).join(', ')}`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const modelName = cliModel || provider.defaultModel;
|
|
387
|
+
const model = provider.models[modelName];
|
|
388
|
+
if (!model) {
|
|
233
389
|
throw fail(
|
|
234
390
|
ERROR_CATEGORIES.CONFIG,
|
|
235
|
-
`Unknown
|
|
391
|
+
`Unknown model: "${modelName}" for provider "${providerName}". ` +
|
|
392
|
+
`Available models: ${Object.keys(provider.models).join(', ')}`,
|
|
236
393
|
);
|
|
237
394
|
}
|
|
238
395
|
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return { config: resolved, providerName: name };
|
|
396
|
+
const providerConfig = mergeConfig(config, providerRuntimeConfig(provider));
|
|
397
|
+
const resolvedConfig = mergeConfig(providerConfig, modelRuntimeConfig(model));
|
|
398
|
+
return { config: resolvedConfig, providerName, modelName };
|
|
243
399
|
}
|
|
244
400
|
|
|
245
401
|
function assertString(config, key) {
|
|
@@ -440,9 +596,13 @@ export function getProjectRoot() {
|
|
|
440
596
|
}
|
|
441
597
|
}
|
|
442
598
|
|
|
443
|
-
export async function loadConfig(
|
|
599
|
+
export async function loadConfig(
|
|
600
|
+
cliProvider = null,
|
|
601
|
+
{ model: cliModel = null, resolveCredentials = true } = {},
|
|
602
|
+
) {
|
|
444
603
|
const projectRoot = getProjectRoot();
|
|
445
604
|
let config = { ...DEFAULT_CONFIG };
|
|
605
|
+
let catalog = DEFAULT_PROVIDER_CATALOG;
|
|
446
606
|
const loaded = [];
|
|
447
607
|
|
|
448
608
|
const userPath = join(homedir(), '.aicommit.config.json');
|
|
@@ -456,13 +616,25 @@ export async function loadConfig(cliProvider = null, { resolveCredentials = true
|
|
|
456
616
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
457
617
|
throw new Error(`Failed to parse user config ${userPath}: expected a JSON object.`);
|
|
458
618
|
}
|
|
459
|
-
|
|
619
|
+
validateUserConfig(parsed);
|
|
620
|
+
catalog = parsed;
|
|
621
|
+
const globalConfig = { ...parsed };
|
|
622
|
+
delete globalConfig.schemaVersion;
|
|
623
|
+
delete globalConfig.defaultProvider;
|
|
624
|
+
delete globalConfig.providers;
|
|
625
|
+
config = mergeConfig(config, globalConfig);
|
|
460
626
|
loaded.push('user');
|
|
461
627
|
// Tighten loose permissions on config files that actually hold a key
|
|
462
628
|
// (a hand-created or older 0644 file would otherwise expose it).
|
|
463
629
|
if (configHasApiKey(parsed)) await chmod(userPath, 0o600).catch(() => {});
|
|
464
630
|
}
|
|
465
631
|
|
|
632
|
+
let {
|
|
633
|
+
config: resolvedConfig,
|
|
634
|
+
providerName,
|
|
635
|
+
modelName,
|
|
636
|
+
} = resolveSelection(config, catalog, cliProvider, cliModel);
|
|
637
|
+
|
|
466
638
|
const projectPath = join(projectRoot, '.aicommit.config.json');
|
|
467
639
|
if (projectPath !== userPath && (await fileExists(projectPath))) {
|
|
468
640
|
let parsed;
|
|
@@ -471,8 +643,8 @@ export async function loadConfig(cliProvider = null, { resolveCredentials = true
|
|
|
471
643
|
} catch (err) {
|
|
472
644
|
throw new Error(`Failed to parse project config ${projectPath}: ${err.message}`);
|
|
473
645
|
}
|
|
474
|
-
const { safe, ignored } = filterProjectConfig(parsed,
|
|
475
|
-
|
|
646
|
+
const { safe, ignored } = filterProjectConfig(parsed, resolvedConfig);
|
|
647
|
+
resolvedConfig = mergeConfig(resolvedConfig, safe);
|
|
476
648
|
loaded.push('project');
|
|
477
649
|
if (ignored.length) {
|
|
478
650
|
console.error(
|
|
@@ -487,7 +659,6 @@ export async function loadConfig(cliProvider = null, { resolveCredentials = true
|
|
|
487
659
|
}
|
|
488
660
|
|
|
489
661
|
const teamPolicy = await readTeamPolicy(projectRoot);
|
|
490
|
-
let { config: resolvedConfig, providerName } = resolveProvider(config, cliProvider);
|
|
491
662
|
|
|
492
663
|
// A repository-owned team policy is a strict, credential-free document.
|
|
493
664
|
// Apply it after selecting the personal provider so provider-scoped user
|
|
@@ -516,6 +687,7 @@ export async function loadConfig(cliProvider = null, { resolveCredentials = true
|
|
|
516
687
|
projectRoot,
|
|
517
688
|
loaded,
|
|
518
689
|
providerName,
|
|
690
|
+
modelName,
|
|
519
691
|
credentialSource: credential.source,
|
|
520
692
|
credentialSourceLabel: credential.sourceLabel,
|
|
521
693
|
credentialWarning: credential.warning || null,
|
package/src/doctor.js
CHANGED
|
@@ -33,7 +33,7 @@ function gitVersion() {
|
|
|
33
33
|
}).trim();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export async function runDoctor(cliProvider = null) {
|
|
36
|
+
export async function runDoctor(cliProvider = null, cliModel = null) {
|
|
37
37
|
const checks = [];
|
|
38
38
|
const warnings = [];
|
|
39
39
|
console.log('\n ' + chalk.cyan.bold('Doctor diagnostics'));
|
|
@@ -60,7 +60,7 @@ export async function runDoctor(cliProvider = null) {
|
|
|
60
60
|
|
|
61
61
|
let loadedConfig;
|
|
62
62
|
try {
|
|
63
|
-
loadedConfig = await loadConfig(cliProvider);
|
|
63
|
+
loadedConfig = await loadConfig(cliProvider, { model: cliModel });
|
|
64
64
|
} catch (err) {
|
|
65
65
|
const classified = classifyError(err);
|
|
66
66
|
addCheck(checks, 'Config', 'fail', classified.message);
|
|
@@ -68,11 +68,13 @@ export async function runDoctor(cliProvider = null) {
|
|
|
68
68
|
throw classified;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const { config, loaded, providerName, credentialSourceLabel, credentialWarning } =
|
|
71
|
+
const { config, loaded, providerName, modelName, credentialSourceLabel, credentialWarning } =
|
|
72
|
+
loadedConfig;
|
|
72
73
|
const extensionHost = await configureExtensionHost(config);
|
|
73
74
|
const adapter = await resolveProviderAdapter(config, getProviderAdapter);
|
|
74
75
|
const provider = providerName || adapter.id;
|
|
75
76
|
addCheck(checks, 'Config', 'pass', loaded.length ? loaded.join(' + ') : 'built-in defaults');
|
|
77
|
+
addCheck(checks, 'Model', 'pass', `${providerName}/${modelName} (${config.modelId})`);
|
|
76
78
|
addCheck(
|
|
77
79
|
checks,
|
|
78
80
|
'Endpoint security',
|
package/src/main.js
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
statusIcon,
|
|
29
29
|
confirmAction,
|
|
30
30
|
editMessage,
|
|
31
|
+
isPromptQuitError,
|
|
31
32
|
vimSelect,
|
|
32
33
|
vimCheckbox,
|
|
33
34
|
} from './ui.js';
|
|
@@ -82,13 +83,14 @@ async function pickPathsToStage(unstaged, untracked, message) {
|
|
|
82
83
|
return picked.flat();
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
async function runMain() {
|
|
86
87
|
// ── CLI arguments ───────────────────────────────────────────────────
|
|
87
88
|
|
|
88
89
|
const {
|
|
89
90
|
targetPath,
|
|
90
91
|
cliLang,
|
|
91
92
|
cliProvider,
|
|
93
|
+
cliModel,
|
|
92
94
|
cliReasoning,
|
|
93
95
|
output,
|
|
94
96
|
debug,
|
|
@@ -147,6 +149,7 @@ export async function main() {
|
|
|
147
149
|
if (configAction) {
|
|
148
150
|
return runConfigCommand(configAction, {
|
|
149
151
|
provider: cliProvider,
|
|
152
|
+
model: cliModel,
|
|
150
153
|
machineOutput,
|
|
151
154
|
});
|
|
152
155
|
}
|
|
@@ -182,12 +185,14 @@ export async function main() {
|
|
|
182
185
|
return applySplitPlan(projectRoot, splitPlanFile, { yes, machineOutput });
|
|
183
186
|
}
|
|
184
187
|
|
|
185
|
-
if (doctor) return runDoctor(cliProvider);
|
|
188
|
+
if (doctor) return runDoctor(cliProvider, cliModel);
|
|
186
189
|
|
|
187
190
|
// ── 1. Config ───────────────────────────────────────────────────────
|
|
188
191
|
|
|
189
|
-
const { config, projectRoot, loaded, providerName, teamPolicyPath } =
|
|
190
|
-
|
|
192
|
+
const { config, projectRoot, loaded, providerName, modelName, teamPolicyPath } = await loadConfig(
|
|
193
|
+
cliProvider,
|
|
194
|
+
{ model: cliModel },
|
|
195
|
+
);
|
|
191
196
|
const extensionHost = await configureExtensionHost(config);
|
|
192
197
|
const selectedProvider = providerName || detectProviderType(config.apiUrl, config.providerType);
|
|
193
198
|
const warnings = [];
|
|
@@ -234,8 +239,8 @@ export async function main() {
|
|
|
234
239
|
}
|
|
235
240
|
const reasoningEnabled = config.reasoning.mode === 'on';
|
|
236
241
|
|
|
237
|
-
const viaCli = cliProvider ? chalk.dim(' (via CLI)') : '';
|
|
238
|
-
const modelLabel =
|
|
242
|
+
const viaCli = cliProvider || cliModel ? chalk.dim(' (via CLI)') : '';
|
|
243
|
+
const modelLabel = `${providerName}/${modelName} (${config.modelId})`;
|
|
239
244
|
console.log(
|
|
240
245
|
' ' + chalk.green('✓') + chalk.dim(` Model: ${sanitizeTerminalText(modelLabel)}${viaCli}`),
|
|
241
246
|
);
|
|
@@ -277,12 +282,14 @@ export async function main() {
|
|
|
277
282
|
console.log(chalk.dim(` config files: ${loaded.join(', ') || '(none — defaults only)'}`));
|
|
278
283
|
console.log(chalk.dim(` cliLang: ${cliLang || '(not set)'}`));
|
|
279
284
|
console.log(chalk.dim(` cliProvider: ${cliProvider || '(not set)'}`));
|
|
285
|
+
console.log(chalk.dim(` cliModel: ${cliModel || '(not set)'}`));
|
|
280
286
|
console.log(
|
|
281
287
|
chalk.dim(
|
|
282
288
|
` reasoning: ${config.reasoning.mode === 'on' ? config.reasoning.effort : config.reasoning.mode}${cliReasoning ? ' (via CLI)' : ''}`,
|
|
283
289
|
),
|
|
284
290
|
);
|
|
285
291
|
console.log(chalk.dim(` providerName: ${providerName || '(not set)'}`));
|
|
292
|
+
console.log(chalk.dim(` modelName: ${modelName || '(not set)'}`));
|
|
286
293
|
console.log(chalk.dim(` split: ${split}`));
|
|
287
294
|
console.log(chalk.dim(` dryRun: ${dryRun}`));
|
|
288
295
|
console.log(chalk.dim(` yes: ${yes}`));
|
|
@@ -766,3 +773,19 @@ export async function main() {
|
|
|
766
773
|
});
|
|
767
774
|
}
|
|
768
775
|
}
|
|
776
|
+
|
|
777
|
+
export async function main() {
|
|
778
|
+
try {
|
|
779
|
+
return await runMain();
|
|
780
|
+
} catch (error) {
|
|
781
|
+
if (!isPromptQuitError(error)) throw error;
|
|
782
|
+
console.log(chalk.dim('\n Quit — no commit was created.\n'));
|
|
783
|
+
return {
|
|
784
|
+
warnings: [],
|
|
785
|
+
exitReason: 'cancelled',
|
|
786
|
+
committed: false,
|
|
787
|
+
edited: false,
|
|
788
|
+
rewrites: 0,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
}
|
package/src/provider-presets.js
CHANGED
|
@@ -11,7 +11,7 @@ const require = createRequire(import.meta.url);
|
|
|
11
11
|
const { version: CORE_VERSION } = require('../package.json');
|
|
12
12
|
|
|
13
13
|
export const PROVIDER_PRESET_KIND = 'aicommit-provider-presets';
|
|
14
|
-
export const PROVIDER_PRESET_SCHEMA_VERSION =
|
|
14
|
+
export const PROVIDER_PRESET_SCHEMA_VERSION = 2;
|
|
15
15
|
export const PROVIDER_ADAPTER_CONTRACT_VERSION = 1;
|
|
16
16
|
export const PROVIDER_PRESET_FILENAME = 'provider-presets.json';
|
|
17
17
|
export const PROVIDER_PRESET_BACKUP_FILENAME = 'provider-presets.previous.json';
|
|
@@ -179,12 +179,8 @@ export function validateProviderPresetManifest(value) {
|
|
|
179
179
|
const ids = new Set();
|
|
180
180
|
for (const [index, provider] of value.providers.entries()) {
|
|
181
181
|
const path = `Provider preset providers[${index}]`;
|
|
182
|
-
const required = ['id', 'label', 'adapter', 'apiUrl', '
|
|
183
|
-
assertExactKeys(
|
|
184
|
-
provider,
|
|
185
|
-
Object.hasOwn(provider || {}, 'extraBody') ? [...required, 'extraBody'] : required,
|
|
186
|
-
path,
|
|
187
|
-
);
|
|
182
|
+
const required = ['id', 'label', 'adapter', 'apiUrl', 'defaultModel', 'models'];
|
|
183
|
+
assertExactKeys(provider, required, path);
|
|
188
184
|
if (
|
|
189
185
|
!ID_RE.test(provider.id) ||
|
|
190
186
|
provider.id.toLowerCase() === 'custom' ||
|
|
@@ -215,17 +211,49 @@ export function validateProviderPresetManifest(value) {
|
|
|
215
211
|
`${path}.apiUrl must be credential-free HTTPS, or HTTP only for localhost/loopback, without query or fragment.`,
|
|
216
212
|
);
|
|
217
213
|
}
|
|
214
|
+
if (!ID_RE.test(provider.defaultModel)) {
|
|
215
|
+
throw new Error(`${path}.defaultModel must be a valid model name.`);
|
|
216
|
+
}
|
|
218
217
|
if (
|
|
219
|
-
|
|
220
|
-
!provider.
|
|
221
|
-
provider.
|
|
222
|
-
/[\0-\x1f\x7f]/.test(provider.modelId)
|
|
218
|
+
!object(provider.models) ||
|
|
219
|
+
!Object.keys(provider.models).length ||
|
|
220
|
+
Object.keys(provider.models).length > 100
|
|
223
221
|
) {
|
|
224
|
-
throw new Error(`${path}.
|
|
222
|
+
throw new Error(`${path}.models must contain 1-100 named models.`);
|
|
223
|
+
}
|
|
224
|
+
if (!Object.hasOwn(provider.models, provider.defaultModel)) {
|
|
225
|
+
throw new Error(`${path}.defaultModel must reference a model in ${path}.models.`);
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
for (const [modelName, model] of Object.entries(provider.models)) {
|
|
228
|
+
const modelPath = `${path}.models.${modelName}`;
|
|
229
|
+
if (!ID_RE.test(modelName)) throw new Error(`${modelPath} has an invalid model name.`);
|
|
230
|
+
const modelKeys = ['modelId'];
|
|
231
|
+
if (Object.hasOwn(model || {}, 'label')) modelKeys.push('label');
|
|
232
|
+
if (Object.hasOwn(model || {}, 'extraBody')) modelKeys.push('extraBody');
|
|
233
|
+
assertExactKeys(model, modelKeys, modelPath);
|
|
234
|
+
if (
|
|
235
|
+
typeof model.modelId !== 'string' ||
|
|
236
|
+
!model.modelId.trim() ||
|
|
237
|
+
model.modelId.length > 256 ||
|
|
238
|
+
/[\0-\x1f\x7f]/.test(model.modelId)
|
|
239
|
+
) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`${modelPath}.modelId must be a non-empty string of at most 256 characters.`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (
|
|
245
|
+
Object.hasOwn(model, 'label') &&
|
|
246
|
+
(typeof model.label !== 'string' ||
|
|
247
|
+
!model.label.trim() ||
|
|
248
|
+
model.label.length > 80 ||
|
|
249
|
+
/[\0-\x1f\x7f]/.test(model.label))
|
|
250
|
+
) {
|
|
251
|
+
throw new Error(`${modelPath}.label must be a non-empty string of at most 80 characters.`);
|
|
252
|
+
}
|
|
253
|
+
if (Object.hasOwn(model, 'extraBody')) {
|
|
254
|
+
if (!object(model.extraBody)) throw new Error(`${modelPath}.extraBody must be an object.`);
|
|
255
|
+
assertExtraBody(model.extraBody, `${modelPath}.extraBody`);
|
|
256
|
+
}
|
|
229
257
|
}
|
|
230
258
|
}
|
|
231
259
|
return value;
|