amicus 3.0.0 → 3.1.0
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +43 -0
- package/README.md +12 -3
- package/electron/setup-ui.js +27 -3
- package/package.json +1 -1
- package/skills/second-opinion/SKILL.md +2 -0
- package/skills/sidecar/SKILL.md +66 -38
- package/src/cli-handlers-resume-continue.js +31 -4
- package/src/cli-handlers-run.js +15 -4
- package/src/cli.js +13 -0
- package/src/mcp-server.js +99 -12
- package/src/mcp-tools.js +26 -4
- package/src/opencode-client.js +18 -2
- package/src/sidecar/continue.js +10 -3
- package/src/sidecar/fanout-leg.js +26 -1
- package/src/sidecar/fanout-output.js +5 -0
- package/src/sidecar/fanout-validate.js +81 -0
- package/src/sidecar/fanout.js +65 -77
- package/src/sidecar/session-utils.js +6 -0
- package/src/sidecar/setup.js +2 -1
- package/src/utils/alias-resolver.js +6 -35
- package/src/utils/api-key-store.js +1 -9
- package/src/utils/auth-json.js +1 -1
- package/src/utils/config.js +98 -16
- package/src/utils/curated-models.js +33 -4
- package/src/utils/gateway-router.js +115 -0
- package/src/utils/input-validators.js +12 -42
- package/src/utils/model-classification.js +65 -0
- package/src/utils/model-descriptor.js +72 -0
- package/src/utils/model-fetcher.js +35 -9
- package/src/utils/model-input-default.js +32 -0
- package/src/utils/model-validator.js +68 -84
- package/src/utils/provider-registry.js +57 -0
- package/src/utils/quick-picks.js +11 -3
- package/src/utils/result-schema-rebuild.js +98 -0
- package/src/utils/result-schema.js +6 -76
- package/src/utils/route-error.js +137 -0
- package/src/utils/route-launch.js +179 -0
- package/src/utils/start-helpers.js +96 -43
- package/src/utils/validators.js +1 -8
|
@@ -6,71 +6,124 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Derive the alias used for resolution (needed downstream by the budget gate,
|
|
10
|
+
* which reports `alias || args.model` as the pricing lookup's modelInput).
|
|
11
|
+
* Per the #61 Task 4.5 design, only ever returns a no-slash token: an
|
|
12
|
+
* explicit `--model` containing '/' or a slash-bearing config default both
|
|
13
|
+
* resolve to undefined here.
|
|
11
14
|
* @param {object} args - Parsed CLI arguments
|
|
12
|
-
* @returns {
|
|
15
|
+
* @returns {string|undefined}
|
|
13
16
|
*/
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
model = resolveModel(args.model);
|
|
20
|
-
} catch (err) {
|
|
21
|
-
console.error(err.message);
|
|
22
|
-
process.exit(1);
|
|
17
|
+
function deriveAlias(args) {
|
|
18
|
+
const raw = args.model;
|
|
19
|
+
if (raw !== undefined && raw !== null && !raw.includes('/')) {
|
|
20
|
+
return raw;
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
let alias = rawAlias;
|
|
27
|
-
if (alias === undefined) {
|
|
22
|
+
if (raw === undefined || raw === null) {
|
|
23
|
+
const { loadConfig } = require('./config');
|
|
28
24
|
const cfg = loadConfig();
|
|
29
25
|
if (cfg && cfg.default && !cfg.default.includes('/')) {
|
|
30
|
-
|
|
26
|
+
return cfg.default;
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
|
-
return
|
|
29
|
+
return undefined;
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
33
|
+
* Resolve the model for a launch through the Foundation gateway router (#61
|
|
34
|
+
* Task 4.5), used by every launch path — start, continue (Task 7.3), and the
|
|
35
|
+
* MCP amicus_start handler (via the same router, Task 6.2). This replaced the
|
|
36
|
+
* legacy resolveModelFromArgs + validateFallbackModel pair, which was retired
|
|
37
|
+
* once no launch path depended on it (#61 Task 4.7).
|
|
38
|
+
*
|
|
39
|
+
* On `resolved`, returns `{ model, alias, gateway, provenance }` (and prints
|
|
40
|
+
* any advisory `notice` to stderr). On `error` or `selection_required`,
|
|
41
|
+
* renders the appropriate message to stderr and exits(1) — this never
|
|
42
|
+
* returns in that case.
|
|
40
43
|
* @param {object} args - Parsed CLI arguments
|
|
41
|
-
* @
|
|
42
|
-
* @returns {Promise<string>} Validated model string
|
|
44
|
+
* @returns {Promise<{model: string, alias: string|undefined, gateway: string, provenance: object}>}
|
|
43
45
|
*/
|
|
44
|
-
async function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
async function resolveLaunchModel(args) {
|
|
47
|
+
const { resolveGatewayMode } = require('./config');
|
|
48
|
+
const { resolveRouteForLaunch } = require('./route-launch');
|
|
49
|
+
const { toCliMessage, toStructuredError } = require('./route-error');
|
|
50
|
+
const { resolveModelInputOrDefault } = require('./model-input-default');
|
|
48
51
|
|
|
49
|
-
const
|
|
50
|
-
const
|
|
52
|
+
const gatewayMode = resolveGatewayMode(args.gateway);
|
|
53
|
+
const validateModel = !args['no-validate-model'];
|
|
54
|
+
// Interactive alternatives picker (#61 Task 6.3): only offered on a real
|
|
55
|
+
// interactive TTY session that hasn't opted out with --no-ui. A headless/
|
|
56
|
+
// non-TTY run (CI, piped output, --no-ui) keeps allowSelection false, so a
|
|
57
|
+
// direct miss there still produces the structured error/selection_required
|
|
58
|
+
// rendered below rather than an unhandled prompt.
|
|
59
|
+
const allowSelection = !args['no-ui'] && !!process.stdin.isTTY;
|
|
60
|
+
|
|
61
|
+
// No --model given: the parser does not inject a default, so resolve the
|
|
62
|
+
// configured default here before handing off to the router. Without this, `undefined`
|
|
63
|
+
// would reach resolveRouteForLaunch -> parseDescriptor(undefined) -> an
|
|
64
|
+
// `invalid` result, breaking the common `amicus start` (no --model) case.
|
|
65
|
+
// Shared with the MCP amicus_start handler (mcp-server.js, #61 Task 6.2)
|
|
66
|
+
// via model-input-default.js, so this lookup lives in exactly one place.
|
|
67
|
+
const modelInput = resolveModelInputOrDefault(args.model);
|
|
68
|
+
if (modelInput === undefined) {
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
'No model specified and no default configured. Run \'amicus setup\' to set a default model.\n'
|
|
71
|
+
);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
51
74
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
const result = await resolveRouteForLaunch({
|
|
76
|
+
model: modelInput,
|
|
77
|
+
gatewayMode,
|
|
78
|
+
source: 'cli',
|
|
79
|
+
allowSelection,
|
|
80
|
+
validateModel,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (result.kind === 'resolved') {
|
|
84
|
+
if (result.notice) {
|
|
85
|
+
process.stderr.write(`${result.notice}\n`);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
model: result.executableId,
|
|
89
|
+
alias: deriveAlias(args),
|
|
90
|
+
gateway: result.gateway,
|
|
91
|
+
provenance: result.provenance,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Interactive alternatives picker (#61 Task 6.3): only reachable when
|
|
96
|
+
// allowSelection was true above (real TTY, no --no-ui), so the router only
|
|
97
|
+
// ever hands back selection_required here in that same interactive case.
|
|
98
|
+
if (result.kind === 'selection_required') {
|
|
99
|
+
const { promptRouteSelection } = require('./model-validator');
|
|
55
100
|
try {
|
|
56
|
-
|
|
101
|
+
const chosen = await promptRouteSelection(result, deriveAlias(args));
|
|
102
|
+
return {
|
|
103
|
+
model: chosen.model,
|
|
104
|
+
alias: deriveAlias(args),
|
|
105
|
+
gateway: chosen.gateway,
|
|
106
|
+
provenance: result.provenance || {},
|
|
107
|
+
};
|
|
57
108
|
} catch (err) {
|
|
58
|
-
|
|
109
|
+
process.stderr.write(`${err.message || 'Model selection cancelled.'}\n`);
|
|
59
110
|
process.exit(1);
|
|
60
111
|
}
|
|
61
112
|
}
|
|
62
113
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
114
|
+
// 'error': render and exit. (A non-interactive run never reaches
|
|
115
|
+
// kind:'selection_required' — allowSelection is false there, so the router
|
|
116
|
+
// resolves a catalog miss straight to kind:'error' with reason
|
|
117
|
+
// 'model_not_found' instead; see gateway-router.js's catalogGate.)
|
|
118
|
+
if (args.json) {
|
|
119
|
+
process.stderr.write(`${JSON.stringify(toStructuredError(result))}\n`);
|
|
120
|
+
} else {
|
|
121
|
+
process.stderr.write(`${toCliMessage(result)}\n`);
|
|
70
122
|
}
|
|
123
|
+
process.exit(1);
|
|
71
124
|
}
|
|
72
125
|
|
|
73
126
|
module.exports = {
|
|
74
|
-
|
|
75
|
-
|
|
127
|
+
resolveLaunchModel,
|
|
128
|
+
deriveAlias,
|
|
76
129
|
};
|
package/src/utils/validators.js
CHANGED
|
@@ -19,13 +19,7 @@ const VALID_AGENT_MODES = OPENCODE_AGENTS;
|
|
|
19
19
|
/**
|
|
20
20
|
* Provider to API key mapping
|
|
21
21
|
*/
|
|
22
|
-
const PROVIDER_KEY_MAP =
|
|
23
|
-
'openrouter': { key: 'OPENROUTER_API_KEY', name: 'OpenRouter' },
|
|
24
|
-
'google': { key: 'GOOGLE_GENERATIVE_AI_API_KEY', name: 'Google Gemini' },
|
|
25
|
-
'openai': { key: 'OPENAI_API_KEY', name: 'OpenAI' },
|
|
26
|
-
'anthropic': { key: 'ANTHROPIC_API_KEY', name: 'Anthropic' },
|
|
27
|
-
'deepseek': { key: 'DEEPSEEK_API_KEY', name: 'DeepSeek' },
|
|
28
|
-
};
|
|
22
|
+
const { PROVIDER_KEY_MAP } = require('./provider-registry');
|
|
29
23
|
|
|
30
24
|
/** Task ID format: alphanumeric, hyphens, underscores, 1-64 chars */
|
|
31
25
|
const TASK_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
@@ -272,5 +266,4 @@ module.exports = {
|
|
|
272
266
|
findSessionInProjectDirs,
|
|
273
267
|
// Re-exported from input-validators.js
|
|
274
268
|
validateStartInputs: require('./input-validators').validateStartInputs,
|
|
275
|
-
findSimilar: require('./input-validators').findSimilar,
|
|
276
269
|
};
|