create-quiver 0.15.0 → 0.15.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/CHANGELOG.md +6 -0
- package/README.md +31 -0
- package/README_FOR_AI.md +8 -3
- package/ROADMAP.md +5 -0
- package/docs/AI_CONTEXT.md.template +4 -0
- package/docs/AI_ONBOARDING_PROMPT.md.template +8 -0
- package/docs/CLI_UX_GUIDE.md +16 -1
- package/docs/COMMANDS.md.template +14 -4
- package/docs/TROUBLESHOOTING.md +70 -0
- package/docs/TROUBLESHOOTING.md.template +20 -0
- package/docs/getting-started/installation.md +86 -0
- package/docs/reference/commands.md +29 -1
- package/package.json +1 -1
- package/specs/quiver-v31-ai-model-catalog-agent-selection/EVIDENCE_REPORT.md +185 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/EXECUTION_PLAN.md +85 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/SPEC.md +337 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/STATUS.md +30 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/pr.md +98 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/slice.json +62 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/slice.json +72 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/slice.json +78 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/slice.json +77 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/slice.json +84 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/slice.json +72 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/slice.json +84 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/slice.json +94 -0
- package/specs/quiver-v32-npx-installation-guidance/EVIDENCE_REPORT.md +26 -0
- package/specs/quiver-v32-npx-installation-guidance/SPEC.md +55 -0
- package/specs/quiver-v32-npx-installation-guidance/STATUS.md +23 -0
- package/specs/quiver-v32-npx-installation-guidance/pr.md +72 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/slice.json +75 -0
- package/src/create-quiver/commands/ai.js +449 -34
- package/src/create-quiver/commands/flow.js +2 -2
- package/src/create-quiver/index.js +26 -10
- package/src/create-quiver/lib/agent-profiles.js +332 -4
- package/src/create-quiver/lib/ai/execution-plan.js +7 -2
- package/src/create-quiver/lib/ai/executor.js +9 -2
- package/src/create-quiver/lib/ai/model-catalog.js +333 -0
- package/src/create-quiver/lib/ai/providers.js +143 -4
- package/src/create-quiver/lib/cli/selectors.js +53 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const { assertSupportedProvider } = require('./providers');
|
|
2
|
+
|
|
3
|
+
const MODEL_CATALOG_VERSION = 1;
|
|
4
|
+
const MODEL_CATALOG_LAST_UPDATED = '2026-05-27';
|
|
5
|
+
|
|
6
|
+
const MODEL_CATALOG = Object.freeze({
|
|
7
|
+
catalogVersion: MODEL_CATALOG_VERSION,
|
|
8
|
+
lastUpdated: MODEL_CATALOG_LAST_UPDATED,
|
|
9
|
+
providers: Object.freeze({
|
|
10
|
+
codex: Object.freeze({
|
|
11
|
+
id: 'codex',
|
|
12
|
+
displayName: 'Codex',
|
|
13
|
+
models: Object.freeze([
|
|
14
|
+
knownModel('gpt-5.5', 'GPT 5.5', {
|
|
15
|
+
aliases: ['GPT 5.5', 'gpt 5.5', 'Gpt-5.5'],
|
|
16
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
17
|
+
costTier: 'high',
|
|
18
|
+
qualityTier: 'premium',
|
|
19
|
+
stability: 'stable',
|
|
20
|
+
notes: 'Recommended for planning, specs, architecture, and high-risk reviews.',
|
|
21
|
+
}),
|
|
22
|
+
knownModel('gpt-5.4', 'GPT 5.4', {
|
|
23
|
+
aliases: ['GPT 5.4', 'gpt 5.4'],
|
|
24
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
25
|
+
costTier: 'medium',
|
|
26
|
+
qualityTier: 'high',
|
|
27
|
+
stability: 'stable',
|
|
28
|
+
notes: 'Balanced planner or reviewer model.',
|
|
29
|
+
}),
|
|
30
|
+
knownModel('gpt-5.4-mini', 'GPT 5.4 mini', {
|
|
31
|
+
aliases: ['GPT 5.4 mini', 'gpt 5.4 mini', 'gpt-5.4 mini'],
|
|
32
|
+
recommendedFor: ['executor', 'doctor'],
|
|
33
|
+
costTier: 'low',
|
|
34
|
+
qualityTier: 'standard',
|
|
35
|
+
stability: 'stable',
|
|
36
|
+
notes: 'Recommended for lower-cost execution, diagnostics, and sub-agent work.',
|
|
37
|
+
}),
|
|
38
|
+
knownModel('gpt-5.3-codex', 'GPT 5.3 Codex', {
|
|
39
|
+
aliases: ['GPT 5.3 Codex', 'gpt 5.3 codex'],
|
|
40
|
+
recommendedFor: ['executor'],
|
|
41
|
+
costTier: 'medium',
|
|
42
|
+
qualityTier: 'high',
|
|
43
|
+
stability: 'stable',
|
|
44
|
+
notes: 'Recommended for more complex coding slices.',
|
|
45
|
+
}),
|
|
46
|
+
knownModel('gpt-5.3-codex-spark', 'GPT 5.3 Codex Spark', {
|
|
47
|
+
aliases: ['GPT 5.3 Codex Spark', 'gpt 5.3 codex spark'],
|
|
48
|
+
recommendedFor: ['executor'],
|
|
49
|
+
costTier: 'medium',
|
|
50
|
+
qualityTier: 'high',
|
|
51
|
+
stability: 'preview',
|
|
52
|
+
notes: 'Preview or limited model for quick code iteration.',
|
|
53
|
+
}),
|
|
54
|
+
]),
|
|
55
|
+
}),
|
|
56
|
+
claude: Object.freeze({
|
|
57
|
+
id: 'claude',
|
|
58
|
+
displayName: 'Claude',
|
|
59
|
+
models: Object.freeze([
|
|
60
|
+
knownModel('opus', 'Claude Opus', {
|
|
61
|
+
aliases: ['Claude Opus', 'opus'],
|
|
62
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
63
|
+
costTier: 'high',
|
|
64
|
+
qualityTier: 'premium',
|
|
65
|
+
stability: 'stable',
|
|
66
|
+
notes: 'Recommended for complex planning and architectural decisions.',
|
|
67
|
+
}),
|
|
68
|
+
knownModel('sonnet', 'Claude Sonnet', {
|
|
69
|
+
aliases: ['Claude Sonnet', 'sonnet'],
|
|
70
|
+
recommendedFor: ['executor', 'reviewer'],
|
|
71
|
+
costTier: 'medium',
|
|
72
|
+
qualityTier: 'high',
|
|
73
|
+
stability: 'stable',
|
|
74
|
+
notes: 'Balanced execution and review model.',
|
|
75
|
+
}),
|
|
76
|
+
knownModel('haiku', 'Claude Haiku', {
|
|
77
|
+
aliases: ['Claude Haiku', 'haiku'],
|
|
78
|
+
recommendedFor: ['doctor'],
|
|
79
|
+
costTier: 'low',
|
|
80
|
+
qualityTier: 'standard',
|
|
81
|
+
stability: 'stable',
|
|
82
|
+
notes: 'Recommended for diagnostics and simple tasks.',
|
|
83
|
+
}),
|
|
84
|
+
knownModel('claude-opus-4-7', 'Claude Opus 4.7', {
|
|
85
|
+
aliases: ['OPUS 4.7', 'Claude Opus 4.7', 'opus 4.7'],
|
|
86
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
87
|
+
costTier: 'high',
|
|
88
|
+
qualityTier: 'premium',
|
|
89
|
+
stability: 'known',
|
|
90
|
+
notes: 'Version-pinned known catalog choice for planning.',
|
|
91
|
+
}),
|
|
92
|
+
knownModel('claude-sonnet-4-6', 'Claude Sonnet 4.6', {
|
|
93
|
+
aliases: ['Claude Sonnet 4.6', 'sonnet 4.6'],
|
|
94
|
+
recommendedFor: ['executor', 'reviewer'],
|
|
95
|
+
costTier: 'medium',
|
|
96
|
+
qualityTier: 'high',
|
|
97
|
+
stability: 'known',
|
|
98
|
+
notes: 'Version-pinned known catalog choice for execution and review.',
|
|
99
|
+
}),
|
|
100
|
+
knownModel('claude-haiku-4-5', 'Claude Haiku 4.5', {
|
|
101
|
+
aliases: ['Claude Haiku 4.5', 'haiku 4.5'],
|
|
102
|
+
recommendedFor: ['doctor'],
|
|
103
|
+
costTier: 'low',
|
|
104
|
+
qualityTier: 'standard',
|
|
105
|
+
stability: 'known',
|
|
106
|
+
notes: 'Version-pinned known catalog choice for low-cost diagnostics.',
|
|
107
|
+
}),
|
|
108
|
+
]),
|
|
109
|
+
}),
|
|
110
|
+
gemini: Object.freeze({
|
|
111
|
+
id: 'gemini',
|
|
112
|
+
displayName: 'Gemini',
|
|
113
|
+
models: Object.freeze([
|
|
114
|
+
knownModel('gemini-3.1-pro-preview', 'Gemini 3.1 Pro Preview', {
|
|
115
|
+
aliases: ['Gemini 3.1 Pro Preview', 'gemini 3.1 pro preview'],
|
|
116
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
117
|
+
costTier: 'high',
|
|
118
|
+
qualityTier: 'premium',
|
|
119
|
+
stability: 'preview',
|
|
120
|
+
notes: 'Known catalog choice for complex planning and review.',
|
|
121
|
+
}),
|
|
122
|
+
knownModel('gemini-3.5-flash', 'Gemini 3.5 Flash', {
|
|
123
|
+
aliases: ['Gemini 3.5 Flash', 'gemini 3.5 flash'],
|
|
124
|
+
recommendedFor: ['executor'],
|
|
125
|
+
costTier: 'medium',
|
|
126
|
+
qualityTier: 'high',
|
|
127
|
+
stability: 'stable',
|
|
128
|
+
notes: 'Known catalog choice for balanced execution.',
|
|
129
|
+
}),
|
|
130
|
+
knownModel('gemini-3.1-flash-lite', 'Gemini 3.1 Flash-Lite', {
|
|
131
|
+
aliases: ['Gemini 3.1 Flash Lite', 'Gemini 3.1 Flash-Lite', 'gemini 3.1 flash lite'],
|
|
132
|
+
recommendedFor: ['doctor'],
|
|
133
|
+
costTier: 'low',
|
|
134
|
+
qualityTier: 'standard',
|
|
135
|
+
stability: 'stable',
|
|
136
|
+
notes: 'Known catalog choice for low-cost diagnostics and mechanical tasks.',
|
|
137
|
+
}),
|
|
138
|
+
knownModel('gemini-3.1-pro-preview-customtools', 'Gemini 3.1 Pro Custom Tools', {
|
|
139
|
+
aliases: ['Gemini 3.1 Pro Custom Tools', 'gemini 3.1 pro preview custom tools'],
|
|
140
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
141
|
+
costTier: 'high',
|
|
142
|
+
qualityTier: 'premium',
|
|
143
|
+
stability: 'preview',
|
|
144
|
+
notes: 'Known catalog choice for advanced tool-oriented workflows.',
|
|
145
|
+
}),
|
|
146
|
+
]),
|
|
147
|
+
}),
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
function knownModel(id, displayName, options = {}) {
|
|
152
|
+
return Object.freeze({
|
|
153
|
+
id,
|
|
154
|
+
displayName,
|
|
155
|
+
aliases: Object.freeze(Array.from(new Set([id, displayName].concat(options.aliases || [])))),
|
|
156
|
+
recommendedFor: Object.freeze((options.recommendedFor || []).slice()),
|
|
157
|
+
costTier: options.costTier || 'unknown',
|
|
158
|
+
qualityTier: options.qualityTier || 'unknown',
|
|
159
|
+
stability: options.stability || 'known',
|
|
160
|
+
notes: options.notes || '',
|
|
161
|
+
modelSource: 'catalog',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function customModel(providerId, modelId = '') {
|
|
166
|
+
const id = String(modelId || '').trim();
|
|
167
|
+
return {
|
|
168
|
+
id: id || 'custom',
|
|
169
|
+
displayName: id || 'Custom',
|
|
170
|
+
aliases: id ? [id] : ['custom'],
|
|
171
|
+
recommendedFor: [],
|
|
172
|
+
costTier: 'unknown',
|
|
173
|
+
qualityTier: 'unknown',
|
|
174
|
+
stability: 'custom',
|
|
175
|
+
notes: 'Custom model id. Quiver cannot guarantee account availability without a live validation.',
|
|
176
|
+
modelSource: 'custom',
|
|
177
|
+
custom: true,
|
|
178
|
+
provider: providerId ? assertSupportedProvider(providerId) : '',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function getModelCatalog() {
|
|
183
|
+
return MODEL_CATALOG;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function listCatalogProviders() {
|
|
187
|
+
return Object.values(MODEL_CATALOG.providers).map((provider) => ({
|
|
188
|
+
id: provider.id,
|
|
189
|
+
displayName: provider.displayName,
|
|
190
|
+
modelCount: provider.models.length,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getProviderModelCatalog(providerId) {
|
|
195
|
+
const provider = assertSupportedProvider(providerId);
|
|
196
|
+
return MODEL_CATALOG.providers[provider];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getKnownModelsForProvider(providerId, options = {}) {
|
|
200
|
+
const provider = getProviderModelCatalog(providerId);
|
|
201
|
+
const role = String(options.role || '').trim().toLowerCase();
|
|
202
|
+
const includeCustom = options.includeCustom !== false;
|
|
203
|
+
const models = provider.models.slice();
|
|
204
|
+
const sorted = role
|
|
205
|
+
? models.sort((left, right) => roleScore(right, role) - roleScore(left, role))
|
|
206
|
+
: models;
|
|
207
|
+
|
|
208
|
+
if (!includeCustom) {
|
|
209
|
+
return sorted;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return sorted.concat(customModel(provider.id));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function roleScore(model, role) {
|
|
216
|
+
const roles = Array.isArray(model.recommendedFor) ? model.recommendedFor : [];
|
|
217
|
+
const index = roles.indexOf(role);
|
|
218
|
+
if (index === -1) return 0;
|
|
219
|
+
return 100 - index;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function normalizeModelAliasKey(value) {
|
|
223
|
+
return String(value || '')
|
|
224
|
+
.trim()
|
|
225
|
+
.toLowerCase()
|
|
226
|
+
.replace(/[^a-z0-9]+/g, '');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function resolveModelFromEntries(providerId, modelValue, entries) {
|
|
230
|
+
const provider = assertSupportedProvider(providerId);
|
|
231
|
+
const input = String(modelValue || '').trim();
|
|
232
|
+
const inputKey = normalizeModelAliasKey(input);
|
|
233
|
+
if (!inputKey) {
|
|
234
|
+
return {
|
|
235
|
+
provider,
|
|
236
|
+
input,
|
|
237
|
+
matched: false,
|
|
238
|
+
ambiguous: false,
|
|
239
|
+
model: null,
|
|
240
|
+
matches: [],
|
|
241
|
+
reason: 'empty model',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const matches = [];
|
|
246
|
+
for (const model of entries || []) {
|
|
247
|
+
const aliases = Array.isArray(model.aliases) ? model.aliases : [model.id, model.displayName];
|
|
248
|
+
if (aliases.some((alias) => normalizeModelAliasKey(alias) === inputKey)) {
|
|
249
|
+
matches.push(model);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (matches.length === 1) {
|
|
254
|
+
const model = matches[0];
|
|
255
|
+
return {
|
|
256
|
+
provider,
|
|
257
|
+
input,
|
|
258
|
+
matched: true,
|
|
259
|
+
ambiguous: false,
|
|
260
|
+
model,
|
|
261
|
+
matches,
|
|
262
|
+
technicalModel: model.id,
|
|
263
|
+
displayName: model.displayName,
|
|
264
|
+
modelSource: model.modelSource || 'catalog',
|
|
265
|
+
reason: 'known model alias matched',
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (matches.length > 1) {
|
|
270
|
+
return {
|
|
271
|
+
provider,
|
|
272
|
+
input,
|
|
273
|
+
matched: false,
|
|
274
|
+
ambiguous: true,
|
|
275
|
+
model: null,
|
|
276
|
+
matches,
|
|
277
|
+
reason: 'ambiguous model alias',
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
provider,
|
|
283
|
+
input,
|
|
284
|
+
matched: false,
|
|
285
|
+
ambiguous: false,
|
|
286
|
+
model: customModel(provider, input),
|
|
287
|
+
matches: [],
|
|
288
|
+
technicalModel: input,
|
|
289
|
+
displayName: input,
|
|
290
|
+
modelSource: 'custom',
|
|
291
|
+
reason: 'custom model',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function resolveKnownModel(providerId, modelValue) {
|
|
296
|
+
const provider = getProviderModelCatalog(providerId);
|
|
297
|
+
return resolveModelFromEntries(provider.id, modelValue, provider.models);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function normalizeModelSelection(providerId, modelValue, options = {}) {
|
|
301
|
+
const resolution = resolveKnownModel(providerId, modelValue);
|
|
302
|
+
const fallbackDisplayName = String(options.displayName || '').trim();
|
|
303
|
+
|
|
304
|
+
if (resolution.ambiguous) {
|
|
305
|
+
return {
|
|
306
|
+
...resolution,
|
|
307
|
+
model: String(modelValue || '').trim(),
|
|
308
|
+
displayName: fallbackDisplayName || String(modelValue || '').trim(),
|
|
309
|
+
validationStatus: options.validationStatus || 'not-tested',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
...resolution,
|
|
315
|
+
model: resolution.technicalModel || String(modelValue || '').trim(),
|
|
316
|
+
displayName: fallbackDisplayName || resolution.displayName || String(modelValue || '').trim(),
|
|
317
|
+
validationStatus: options.validationStatus || 'not-tested',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
module.exports = {
|
|
322
|
+
MODEL_CATALOG_LAST_UPDATED,
|
|
323
|
+
MODEL_CATALOG_VERSION,
|
|
324
|
+
customModel,
|
|
325
|
+
getKnownModelsForProvider,
|
|
326
|
+
getModelCatalog,
|
|
327
|
+
getProviderModelCatalog,
|
|
328
|
+
listCatalogProviders,
|
|
329
|
+
normalizeModelAliasKey,
|
|
330
|
+
normalizeModelSelection,
|
|
331
|
+
resolveKnownModel,
|
|
332
|
+
resolveModelFromEntries,
|
|
333
|
+
};
|
|
@@ -72,9 +72,54 @@ function normalizeProviderModel(model) {
|
|
|
72
72
|
return value || '';
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function resolveProviderModelSelection(providerId, model, options = {}) {
|
|
76
|
+
const rawModel = normalizeProviderModel(model);
|
|
77
|
+
if (!rawModel) {
|
|
78
|
+
return {
|
|
79
|
+
input: '',
|
|
80
|
+
model: '',
|
|
81
|
+
displayName: '',
|
|
82
|
+
modelSource: '',
|
|
83
|
+
aliasNormalized: false,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { normalizeModelSelection } = require('./model-catalog');
|
|
88
|
+
const selection = normalizeModelSelection(providerId, rawModel, {
|
|
89
|
+
displayName: options.displayName,
|
|
90
|
+
});
|
|
91
|
+
const aliasNormalized = selection.modelSource === 'catalog' && selection.model !== rawModel;
|
|
92
|
+
if (aliasNormalized && options.blockModelAlias === true) {
|
|
93
|
+
throw new ProviderRunnerError(
|
|
94
|
+
'DISPLAY_MODEL_ALIAS',
|
|
95
|
+
`Model '${rawModel}' is a display alias for provider '${providerId}', not the technical id. Use '${selection.model}' or run 'npx create-quiver ai agent repair --dry-run'.`,
|
|
96
|
+
{
|
|
97
|
+
provider: providerId,
|
|
98
|
+
inputModel: rawModel,
|
|
99
|
+
suggestedModel: selection.model,
|
|
100
|
+
displayName: selection.displayName,
|
|
101
|
+
nextSteps: [
|
|
102
|
+
`Use --model ${selection.model}.`,
|
|
103
|
+
'Run npx create-quiver ai agent repair --dry-run to preview profile normalization.',
|
|
104
|
+
'Run npx create-quiver ai agent doctor to inspect profile issues.',
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
input: rawModel,
|
|
112
|
+
model: selection.model,
|
|
113
|
+
displayName: selection.displayName,
|
|
114
|
+
modelSource: selection.modelSource,
|
|
115
|
+
aliasNormalized,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
75
119
|
function buildProviderModelArgs(providerId, model, options = {}) {
|
|
76
120
|
const provider = getProviderDefinition(providerId);
|
|
77
|
-
const
|
|
121
|
+
const modelResolution = resolveProviderModelSelection(provider.id, model, options);
|
|
122
|
+
const normalizedModel = modelResolution.model;
|
|
78
123
|
const enforce = options.enforce === true;
|
|
79
124
|
|
|
80
125
|
if (!normalizedModel) {
|
|
@@ -114,13 +159,19 @@ function buildProviderModelArgs(providerId, model, options = {}) {
|
|
|
114
159
|
}
|
|
115
160
|
|
|
116
161
|
const args = provider.modelArgBuilder(normalizedModel);
|
|
117
|
-
|
|
162
|
+
const result = {
|
|
118
163
|
model: normalizedModel,
|
|
119
164
|
supported: true,
|
|
120
165
|
enforced: enforce,
|
|
121
166
|
args: Array.isArray(args) ? args.map((arg) => String(arg)) : [],
|
|
122
|
-
reason: 'model argument supported',
|
|
167
|
+
reason: modelResolution.aliasNormalized ? `model alias normalized from ${modelResolution.input}` : 'model argument supported',
|
|
123
168
|
};
|
|
169
|
+
if (modelResolution.aliasNormalized) {
|
|
170
|
+
result.input = modelResolution.input;
|
|
171
|
+
result.displayName = modelResolution.displayName;
|
|
172
|
+
result.modelSource = modelResolution.modelSource;
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
124
175
|
}
|
|
125
176
|
|
|
126
177
|
function buildProviderInvocation(providerId, options = {}) {
|
|
@@ -128,6 +179,8 @@ function buildProviderInvocation(providerId, options = {}) {
|
|
|
128
179
|
const extraArgs = Array.isArray(options.args) ? options.args.map((arg) => String(arg)) : [];
|
|
129
180
|
const modelSelection = buildProviderModelArgs(provider.id, options.model, {
|
|
130
181
|
enforce: options.enforceModelSelection === true,
|
|
182
|
+
blockModelAlias: options.blockModelAlias === true,
|
|
183
|
+
displayName: options.displayName,
|
|
131
184
|
});
|
|
132
185
|
const prompt = String(options.prompt ?? '');
|
|
133
186
|
const timeoutMs = Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : provider.timeoutMs;
|
|
@@ -184,6 +237,85 @@ function serializeError(error, provider, invocation) {
|
|
|
184
237
|
};
|
|
185
238
|
}
|
|
186
239
|
|
|
240
|
+
function compactProviderText(value) {
|
|
241
|
+
return redactSecrets(String(value || ''))
|
|
242
|
+
.replace(/\bsk-[A-Za-z0-9_-]{16,}\b/g, '[REDACTED]')
|
|
243
|
+
.replace(/\bghp_[A-Za-z0-9_]{16,}\b/g, '[REDACTED]')
|
|
244
|
+
.replace(/\bgithub_pat_[A-Za-z0-9_]{16,}\b/g, '[REDACTED]')
|
|
245
|
+
.split(/\r?\n/)
|
|
246
|
+
.map((line) => line.trim())
|
|
247
|
+
.filter(Boolean)
|
|
248
|
+
.slice(0, 6)
|
|
249
|
+
.join(' ');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function extractProviderErrorCause(result = {}) {
|
|
253
|
+
const error = result.error || {};
|
|
254
|
+
const text = compactProviderText([
|
|
255
|
+
error.message,
|
|
256
|
+
result.stderr,
|
|
257
|
+
result.stdout,
|
|
258
|
+
].filter(Boolean).join('\n'));
|
|
259
|
+
const lower = text.toLowerCase();
|
|
260
|
+
|
|
261
|
+
if (error.code === 'MISSING_PROVIDER_CLI') {
|
|
262
|
+
return {
|
|
263
|
+
code: 'MISSING_PROVIDER_CLI',
|
|
264
|
+
message: error.message || `Provider CLI '${result.command || result.provider}' is not available.`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (error.code === 'PROVIDER_TIMEOUT') {
|
|
269
|
+
return {
|
|
270
|
+
code: 'PROVIDER_TIMEOUT',
|
|
271
|
+
message: error.message || `Provider '${result.provider}' timed out.`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const quotedUnsupportedModel = text.match(/the ['"`]([^'"`]+)['"`] model is not supported/i);
|
|
276
|
+
const genericInvalidModel = /invalid model|unknown model|model .*not supported|model .*not available|model .*does not exist|unsupported model/i.test(text);
|
|
277
|
+
if (quotedUnsupportedModel || genericInvalidModel) {
|
|
278
|
+
const model = quotedUnsupportedModel ? quotedUnsupportedModel[1] : result.modelSelection?.model;
|
|
279
|
+
return {
|
|
280
|
+
code: 'INVALID_PROVIDER_MODEL',
|
|
281
|
+
message: model
|
|
282
|
+
? `Provider '${result.provider}' rejected model '${model}'. ${text}`
|
|
283
|
+
: `Provider '${result.provider}' rejected the selected model. ${text}`,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (/unauthorized|forbidden|permission denied|not authenticated|authentication|api key|login required|log in/i.test(text)) {
|
|
288
|
+
return {
|
|
289
|
+
code: 'PROVIDER_AUTH_ERROR',
|
|
290
|
+
message: `Provider '${result.provider}' authentication failed. ${text}`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (text) {
|
|
295
|
+
return {
|
|
296
|
+
code: 'PROVIDER_RUN_FAILED',
|
|
297
|
+
message: text,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
code: 'PROVIDER_RUN_FAILED',
|
|
303
|
+
message: 'provider run failed',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function createProviderFailureError(result = {}) {
|
|
308
|
+
const cause = extractProviderErrorCause(result);
|
|
309
|
+
return new ProviderRunnerError(cause.code, cause.message, {
|
|
310
|
+
provider: result.provider,
|
|
311
|
+
command: result.command,
|
|
312
|
+
args: Array.isArray(result.args) ? result.args.slice() : [],
|
|
313
|
+
exitCode: result.exitCode,
|
|
314
|
+
signal: result.signal,
|
|
315
|
+
modelSelection: result.modelSelection,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
187
319
|
function runSpawn(command, args, options = {}) {
|
|
188
320
|
const spawnImpl = options.spawn || spawn;
|
|
189
321
|
|
|
@@ -352,7 +484,7 @@ async function runProvider(providerId, options = {}) {
|
|
|
352
484
|
timeoutMs: invocation.timeoutMs,
|
|
353
485
|
});
|
|
354
486
|
|
|
355
|
-
|
|
487
|
+
const providerResult = {
|
|
356
488
|
ok: execution.ok,
|
|
357
489
|
dryRun: false,
|
|
358
490
|
provider: invocation.provider,
|
|
@@ -369,6 +501,10 @@ async function runProvider(providerId, options = {}) {
|
|
|
369
501
|
error: execution.error,
|
|
370
502
|
preflight: preflightResult,
|
|
371
503
|
};
|
|
504
|
+
if (!providerResult.ok && !providerResult.error) {
|
|
505
|
+
providerResult.error = serializeError(createProviderFailureError(providerResult), invocation.provider, invocation);
|
|
506
|
+
}
|
|
507
|
+
return providerResult;
|
|
372
508
|
} finally {
|
|
373
509
|
finalizePromptTransport(transport);
|
|
374
510
|
}
|
|
@@ -380,7 +516,10 @@ module.exports = {
|
|
|
380
516
|
assertSupportedProvider,
|
|
381
517
|
buildProviderModelArgs,
|
|
382
518
|
buildProviderInvocation,
|
|
519
|
+
createProviderFailureError,
|
|
520
|
+
extractProviderErrorCause,
|
|
383
521
|
formatProviderList,
|
|
384
522
|
getProviderDefinition,
|
|
523
|
+
resolveProviderModelSelection,
|
|
385
524
|
runProvider,
|
|
386
525
|
};
|
|
@@ -101,7 +101,60 @@ async function selectOption(message, rawOptions, options = {}) {
|
|
|
101
101
|
].join('\n')));
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
async function promptText(message, options = {}) {
|
|
105
|
+
const explicit = String(options.value || '').trim();
|
|
106
|
+
if (explicit) return explicit;
|
|
107
|
+
|
|
108
|
+
const mode = resolveUxMode({
|
|
109
|
+
interactive: options.interactive === true,
|
|
110
|
+
json: options.json,
|
|
111
|
+
noColor: options.noColor,
|
|
112
|
+
stdinIsTTY: options.stdinIsTTY,
|
|
113
|
+
stdoutIsTTY: options.stdoutIsTTY,
|
|
114
|
+
stderrIsTTY: options.stderrIsTTY,
|
|
115
|
+
}, options.env || process.env, {
|
|
116
|
+
input: options.input || process.stdin,
|
|
117
|
+
output: options.output || process.stdout,
|
|
118
|
+
error: options.error || process.stderr,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (mode.usePrompts) {
|
|
122
|
+
if (typeof options.promptText === 'function') {
|
|
123
|
+
const value = await options.promptText(message, options);
|
|
124
|
+
const normalized = String(value || '').trim();
|
|
125
|
+
if (!normalized && options.required !== false) {
|
|
126
|
+
throw new Error(formatError(`${options.name || 'input'} is required.`));
|
|
127
|
+
}
|
|
128
|
+
return normalized;
|
|
129
|
+
}
|
|
130
|
+
const clack = await loadClack(options.prompts);
|
|
131
|
+
const value = await clack.text({
|
|
132
|
+
message,
|
|
133
|
+
placeholder: options.placeholder,
|
|
134
|
+
initialValue: options.initialValue,
|
|
135
|
+
validate: options.required === false
|
|
136
|
+
? undefined
|
|
137
|
+
: (input) => (String(input || '').trim() ? undefined : 'Required'),
|
|
138
|
+
});
|
|
139
|
+
if (clack.isCancel && clack.isCancel(value)) {
|
|
140
|
+
throw new Error(formatError(`${options.name || 'input'} was canceled.`));
|
|
141
|
+
}
|
|
142
|
+
return String(value || '').trim();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (options.defaultValue !== undefined && options.defaultValue !== null) {
|
|
146
|
+
return String(options.defaultValue).trim();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const flag = options.flag || '--value';
|
|
150
|
+
throw new Error(formatError([
|
|
151
|
+
`${options.name || 'input'} requires an explicit value.`,
|
|
152
|
+
`Use ${flag} <value> or rerun with --interactive.`,
|
|
153
|
+
].join('\n')));
|
|
154
|
+
}
|
|
155
|
+
|
|
104
156
|
module.exports = {
|
|
105
157
|
normalizeSelectorOptions,
|
|
158
|
+
promptText,
|
|
106
159
|
selectOption,
|
|
107
160
|
};
|