pi-namespace-patch 1.2.0-namespace.1 → 1.3.0-namespace.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 +46 -0
- package/README.md +7 -4
- package/dist/bundle/chunks/anthropic-messages-UIH6DSZT.js +6 -0
- package/dist/bundle/chunks/{azure-openai-responses-LM52U7CK.js → azure-openai-responses-KZZFKCXW.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +5 -2
- package/dist/bundle/chunks/{chunk-WBQBCFWG.js → chunk-2JW42LFQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-CQ3XTLTY.js → chunk-2LOB3CBH.js} +2 -2
- package/dist/bundle/chunks/{chunk-H7S5Z6OB.js → chunk-H2VI33AU.js} +17 -17
- package/dist/bundle/chunks/{chunk-TRDNDS6A.js → chunk-PYGXRV27.js} +5 -2
- package/dist/bundle/chunks/{chunk-D7PHKNBM.js → chunk-RH6M5TBT.js} +10 -10
- package/dist/bundle/chunks/{chunk-C2AEIDJZ.js → chunk-RXG5KZNJ.js} +1 -1
- package/dist/bundle/chunks/chunk-UCKJ6OH5.js +11 -0
- package/dist/bundle/chunks/{execute-3CFUPDLS.js → execute-HXNYK3DM.js} +3 -3
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-DXKP72HC.js → google-generative-ai-KJOKBJM3.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-W6SIDOVV.js → google-vertex-VCLZ36OB.js} +1 -1
- package/dist/bundle/chunks/mistral-conversations-AMQJ43DD.js +5 -0
- package/dist/bundle/chunks/{openai-codex-responses-RN6B5UPP.js → openai-codex-responses-ZYGHVE5B.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-QW2B4SML.js → openai-completions-BGCEXFAB.js} +1 -1
- package/dist/bundle/chunks/openai-responses-KCCCXI3B.js +3 -0
- package/dist/bundle/chunks/{pi-messages-YHKPQIHC.js → pi-messages-V27DSLVC.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-KHZAOPVZ.js → virtual-modules-ZXBODXVN.js} +1 -1
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/credential-print.d.ts.map +1 -1
- package/dist/cli/credential-print.js +4 -4
- package/dist/cli/credential-print.js.map +1 -1
- package/dist/core/agent-session.d.ts +4 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/export-html/template.js +21 -16
- package/dist/core/model-config.d.ts +24 -8
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-resolver.d.ts +56 -43
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +247 -352
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +7 -2
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +1 -0
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +1 -0
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -2
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-schema.d.ts +2 -0
- package/dist/core/settings-schema.d.ts.map +1 -1
- package/dist/core/settings-schema.js +4 -0
- package/dist/core/settings-schema.js.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +2 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +21 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/renderers/read.d.ts.map +1 -1
- package/dist/core/tools/renderers/read.js +7 -11
- package/dist/core/tools/renderers/read.js.map +1 -1
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +12 -0
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +2 -9
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +9 -3
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/session-share.d.ts.map +1 -1
- package/dist/modes/interactive/session-share.js +9 -2
- package/dist/modes/interactive/session-share.js.map +1 -1
- package/docs/cli.md +3 -3
- package/docs/codemode.md +2 -0
- package/docs/docs.json +4 -0
- package/docs/llama-cpp.md +1 -1
- package/docs/nvidia-rtx-llama-cpp.md +96 -0
- package/docs/sessions.md +1 -1
- package/docs/settings.md +1 -0
- package/docs/usage.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +4 -5
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/index.ts +4 -6
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +6 -6
- package/schemas/models.schema.json +5 -5
- package/schemas/settings.schema.json +18 -0
- package/dist/bundle/chunks/anthropic-messages-Q7SXESRX.js +0 -6
- package/dist/bundle/chunks/chunk-TN4NLWHF.js +0 -11
- package/dist/bundle/chunks/mistral-conversations-GBMHTSYF.js +0 -5
- package/dist/bundle/chunks/openai-responses-KDJUSIU7.js +0 -3
|
@@ -50,88 +50,163 @@ export const defaultModelPerProvider = {
|
|
|
50
50
|
"xiaomi-token-plan-ams": "mimo-v2.5-pro",
|
|
51
51
|
"xiaomi-token-plan-sgp": "mimo-v2.5-pro",
|
|
52
52
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (id.endsWith("-latest"))
|
|
60
|
-
return true;
|
|
61
|
-
// Check if ID ends with a date pattern (-YYYYMMDD)
|
|
62
|
-
const datePattern = /-\d{8}$/;
|
|
63
|
-
return !datePattern.test(id);
|
|
53
|
+
function splitThinkingSuffix(text) {
|
|
54
|
+
const colonIndex = text.lastIndexOf(":");
|
|
55
|
+
const suffix = text.substring(colonIndex + 1);
|
|
56
|
+
if (colonIndex === -1 || !isValidThinkingLevel(suffix))
|
|
57
|
+
return undefined;
|
|
58
|
+
return { base: text.substring(0, colonIndex), thinkingLevel: suffix };
|
|
64
59
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* When matching by bare id, ambiguous matches across providers are rejected.
|
|
69
|
-
*/
|
|
70
|
-
export function findExactModelReferenceMatch(modelReference, availableModels) {
|
|
71
|
-
const trimmedReference = modelReference.trim();
|
|
72
|
-
if (!trimmedReference) {
|
|
60
|
+
function splitProviderPrefix(text, models) {
|
|
61
|
+
const slashIndex = text.indexOf("/");
|
|
62
|
+
if (slashIndex === -1)
|
|
73
63
|
return undefined;
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
if (canonicalMatches.length === 1) {
|
|
78
|
-
return canonicalMatches[0];
|
|
79
|
-
}
|
|
80
|
-
if (canonicalMatches.length > 1) {
|
|
64
|
+
const prefix = text.substring(0, slashIndex).trim().toLowerCase();
|
|
65
|
+
const modelText = text.substring(slashIndex + 1).trim();
|
|
66
|
+
if (!prefix || !modelText)
|
|
81
67
|
return undefined;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (slashIndex !== -1) {
|
|
85
|
-
const provider = trimmedReference.substring(0, slashIndex).trim();
|
|
86
|
-
const modelId = trimmedReference.substring(slashIndex + 1).trim();
|
|
87
|
-
if (provider && modelId) {
|
|
88
|
-
const providerMatches = availableModels.filter((model) => model.provider.toLowerCase() === provider.toLowerCase() &&
|
|
89
|
-
model.id.toLowerCase() === modelId.toLowerCase());
|
|
90
|
-
if (providerMatches.length === 1) {
|
|
91
|
-
return providerMatches[0];
|
|
92
|
-
}
|
|
93
|
-
if (providerMatches.length > 1) {
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const idMatches = availableModels.filter((model) => model.id.toLowerCase() === normalizedReference);
|
|
99
|
-
return idMatches.length === 1 ? idMatches[0] : undefined;
|
|
68
|
+
const provider = models.find((model) => model.provider.toLowerCase() === prefix)?.provider;
|
|
69
|
+
return provider ? { provider, modelText } : undefined;
|
|
100
70
|
}
|
|
101
71
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
72
|
+
* `usable` only breaks ties between providers, so a unique match resolves even without credentials
|
|
73
|
+
* (`--api-key` first-time setup).
|
|
104
74
|
*/
|
|
105
|
-
function
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
75
|
+
export function resolveModelReference(request, models, usable) {
|
|
76
|
+
const text = request.text.trim();
|
|
77
|
+
if (!text)
|
|
78
|
+
return { status: "not-found" };
|
|
79
|
+
const constraint = request.provider?.toLowerCase();
|
|
80
|
+
const pool = constraint ? models.filter((model) => model.provider.toLowerCase() === constraint) : models;
|
|
81
|
+
const result = resolveText(text, request, pool, usable);
|
|
82
|
+
if (result.status !== "not-found")
|
|
83
|
+
return result;
|
|
84
|
+
const split = splitProviderPrefix(text, pool);
|
|
85
|
+
const anchor = constraint ? pool[0] : pool.find((model) => model.provider === split?.provider);
|
|
86
|
+
if (!anchor)
|
|
87
|
+
return result;
|
|
88
|
+
const modelText = split?.modelText ?? text;
|
|
89
|
+
const suffix = request.thinkingSuffix === "none" ? undefined : splitThinkingSuffix(modelText);
|
|
90
|
+
return {
|
|
91
|
+
status: "not-found",
|
|
92
|
+
target: {
|
|
93
|
+
provider: anchor.provider,
|
|
94
|
+
providerModels: [anchor, ...pool.filter((model) => model.provider === anchor.provider && model !== anchor)],
|
|
95
|
+
modelText,
|
|
96
|
+
modelId: suffix?.base ?? modelText,
|
|
97
|
+
thinkingLevel: suffix?.thinkingLevel,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Exact `provider/id` or bare id lookup without thinking suffixes or fuzzy matching. */
|
|
102
|
+
export function findExactModelReferenceMatch(modelReference, models) {
|
|
103
|
+
const result = resolveModelReference({ text: modelReference, thinkingSuffix: "none", fuzzy: false }, models, () => true);
|
|
104
|
+
return result.status === "resolved" ? result.model : undefined;
|
|
105
|
+
}
|
|
106
|
+
function resolveText(text, request, pool, usable) {
|
|
107
|
+
const reference = selectReference(text, pool, request.fuzzy, usable);
|
|
108
|
+
if (reference)
|
|
109
|
+
return reference;
|
|
110
|
+
if (request.fuzzy) {
|
|
111
|
+
const fuzzy = selectFuzzy(fuzzyMatches(text, pool), usable);
|
|
112
|
+
if (fuzzy)
|
|
113
|
+
return fuzzy;
|
|
114
|
+
}
|
|
115
|
+
if (request.thinkingSuffix === "none")
|
|
116
|
+
return { status: "not-found" };
|
|
117
|
+
const thinking = splitThinkingSuffix(text);
|
|
118
|
+
if (thinking) {
|
|
119
|
+
const inner = resolveText(thinking.base, request, pool, usable);
|
|
120
|
+
if (inner.status === "not-found")
|
|
121
|
+
return inner;
|
|
122
|
+
// Only use this thinking level if no warning from inner recursion
|
|
123
|
+
return { ...inner, thinkingLevel: inner.warning ? undefined : thinking.thinkingLevel };
|
|
124
|
+
}
|
|
125
|
+
// Keep an invalid suffix as part of the id and fail, rather than resolve a different model
|
|
126
|
+
if (request.thinkingSuffix === "strict")
|
|
127
|
+
return { status: "not-found" };
|
|
128
|
+
const colonIndex = text.lastIndexOf(":");
|
|
129
|
+
if (colonIndex === -1)
|
|
130
|
+
return { status: "not-found" };
|
|
131
|
+
const suffix = text.substring(colonIndex + 1);
|
|
132
|
+
const inner = resolveText(text.substring(0, colonIndex), request, pool, usable);
|
|
133
|
+
if (inner.status === "not-found")
|
|
134
|
+
return inner;
|
|
135
|
+
return {
|
|
136
|
+
...inner,
|
|
137
|
+
thinkingLevel: undefined,
|
|
138
|
+
warning: `Invalid thinking level "${suffix}" in pattern "${text}". Using default instead.`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* `provider/rest` is only a hint: the whole text may also be a model id on another provider, such as
|
|
143
|
+
* gateway id `zai/glm-5` or commandcode id `xiaomi/mimo-v2.5-pro`. The named provider wins when usable,
|
|
144
|
+
* since that is what the syntax usually means; otherwise a sole usable raw match wins, since the named
|
|
145
|
+
* provider could not serve the request. An unusable named match is still preferred over unusable raw
|
|
146
|
+
* matches, so `--api-key` can set up the named provider.
|
|
147
|
+
*/
|
|
148
|
+
function selectReference(text, pool, fuzzy, usable) {
|
|
149
|
+
const lowerText = text.toLowerCase();
|
|
150
|
+
const raw = pool.filter((model) => model.id.toLowerCase() === lowerText);
|
|
151
|
+
const split = splitProviderPrefix(text, pool);
|
|
152
|
+
let named;
|
|
153
|
+
if (split) {
|
|
154
|
+
const providerModels = pool.filter((model) => model.provider === split.provider);
|
|
155
|
+
const lowerModelText = split.modelText.toLowerCase();
|
|
156
|
+
// The provider's own literal id (OpenRouter's `openrouter/free`) beats its fuzzy matches
|
|
157
|
+
named =
|
|
158
|
+
providerModels.find((model) => model.id.toLowerCase() === lowerModelText) ??
|
|
159
|
+
raw.find((model) => model.provider === split.provider) ??
|
|
160
|
+
(fuzzy ? bestFuzzyMatch(fuzzyMatches(split.modelText, providerModels)) : undefined);
|
|
161
|
+
}
|
|
162
|
+
const others = raw.filter((model) => model !== named);
|
|
163
|
+
const candidates = named ? [named, ...others] : others;
|
|
164
|
+
if (candidates.length === 0)
|
|
114
165
|
return undefined;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// No alias found, pick latest dated version
|
|
126
|
-
datedVersions.sort((a, b) => b.id.localeCompare(a.id));
|
|
127
|
-
return datedVersions[0];
|
|
128
|
-
}
|
|
166
|
+
if (new Set(candidates.map((model) => model.provider)).size === 1)
|
|
167
|
+
return { status: "resolved", model: candidates[0] };
|
|
168
|
+
if (named && usable(named.provider))
|
|
169
|
+
return { status: "resolved", model: named };
|
|
170
|
+
const usableOthers = others.filter((model) => usable(model.provider));
|
|
171
|
+
if (usableOthers.length === 1)
|
|
172
|
+
return { status: "resolved", model: usableOthers[0] };
|
|
173
|
+
if (usableOthers.length === 0 && named)
|
|
174
|
+
return { status: "resolved", model: named };
|
|
175
|
+
return { status: "ambiguous", candidates, usableCount: usableOthers.length };
|
|
129
176
|
}
|
|
130
|
-
function
|
|
131
|
-
const
|
|
132
|
-
|
|
177
|
+
function fuzzyMatches(text, models) {
|
|
178
|
+
const lowerText = text.toLowerCase();
|
|
179
|
+
return models.filter((model) => model.id.toLowerCase().includes(lowerText) || model.name?.toLowerCase().includes(lowerText));
|
|
180
|
+
}
|
|
181
|
+
function selectFuzzy(matches, usable) {
|
|
182
|
+
if (matches.length === 0)
|
|
183
|
+
return undefined;
|
|
184
|
+
const multipleProviders = new Set(matches.map((model) => model.provider)).size > 1;
|
|
185
|
+
const usableMatches = multipleProviders ? matches.filter((model) => usable(model.provider)) : [];
|
|
186
|
+
const pool = usableMatches.length > 0 ? usableMatches : matches;
|
|
187
|
+
const best = bestFuzzyMatch(pool);
|
|
188
|
+
if (!best)
|
|
133
189
|
return undefined;
|
|
134
|
-
|
|
190
|
+
// The same id on several providers would otherwise be decided by catalog order
|
|
191
|
+
const tied = pool.filter((model) => model.id === best.id);
|
|
192
|
+
if (new Set(tied.map((model) => model.provider)).size === 1)
|
|
193
|
+
return { status: "resolved", model: best };
|
|
194
|
+
return {
|
|
195
|
+
status: "ambiguous",
|
|
196
|
+
candidates: tied,
|
|
197
|
+
usableCount: usableMatches.length > 0 ? tied.length : 0,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/** Prefer aliases (`claude-sonnet-4-5`) over dated versions (`claude-sonnet-4-5-20250929`), then the latest. */
|
|
201
|
+
function bestFuzzyMatch(matches) {
|
|
202
|
+
const aliases = matches.filter((model) => isAlias(model.id));
|
|
203
|
+
return [...(aliases.length > 0 ? aliases : matches)].sort((a, b) => b.id.localeCompare(a.id))[0];
|
|
204
|
+
}
|
|
205
|
+
function isAlias(id) {
|
|
206
|
+
return id.endsWith("-latest") || !/-\d{8}$/.test(id);
|
|
207
|
+
}
|
|
208
|
+
function buildFallbackModel(providerModels, modelId) {
|
|
209
|
+
const defaultId = defaultModelPerProvider[providerModels[0].provider];
|
|
135
210
|
const baseModel = defaultId
|
|
136
211
|
? (providerModels.find((m) => m.id === defaultId) ?? providerModels[0])
|
|
137
212
|
: providerModels[0];
|
|
@@ -142,129 +217,70 @@ function buildFallbackModel(provider, modelId, availableModels) {
|
|
|
142
217
|
};
|
|
143
218
|
}
|
|
144
219
|
/**
|
|
145
|
-
*
|
|
146
|
-
* Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).
|
|
220
|
+
* Resolve scope patterns (`--models`, `enabledModels`) to models with optional thinking levels.
|
|
147
221
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* 2. If found, return it with "off" thinking level
|
|
151
|
-
* 3. If not found and has colons, split on last colon:
|
|
152
|
-
* - If suffix is valid thinking level, use it and recurse on prefix
|
|
153
|
-
* - If suffix is invalid, warn and recurse on prefix with "off"
|
|
154
|
-
*
|
|
155
|
-
* @internal Exported for testing
|
|
222
|
+
* Every model in `models` is usable, so credentials cannot choose between providers offering the same
|
|
223
|
+
* id. All of them are added, with a warning so the user can pick one with `provider/id`.
|
|
156
224
|
*/
|
|
157
|
-
export function parseModelPattern(pattern, availableModels, options) {
|
|
158
|
-
// Try exact match first
|
|
159
|
-
const exactMatch = tryMatchModel(pattern, availableModels);
|
|
160
|
-
if (exactMatch) {
|
|
161
|
-
return { model: exactMatch, thinkingLevel: undefined, warning: undefined };
|
|
162
|
-
}
|
|
163
|
-
// No match - try splitting on last colon if present
|
|
164
|
-
const lastColonIndex = pattern.lastIndexOf(":");
|
|
165
|
-
if (lastColonIndex === -1) {
|
|
166
|
-
// No colons, pattern simply doesn't match any model
|
|
167
|
-
return { model: undefined, thinkingLevel: undefined, warning: undefined };
|
|
168
|
-
}
|
|
169
|
-
const prefix = pattern.substring(0, lastColonIndex);
|
|
170
|
-
const suffix = pattern.substring(lastColonIndex + 1);
|
|
171
|
-
if (isValidThinkingLevel(suffix)) {
|
|
172
|
-
// Valid thinking level - recurse on prefix and use this level
|
|
173
|
-
const result = parseModelPattern(prefix, availableModels, options);
|
|
174
|
-
if (result.model) {
|
|
175
|
-
// Only use this thinking level if no warning from inner recursion
|
|
176
|
-
return {
|
|
177
|
-
model: result.model,
|
|
178
|
-
thinkingLevel: result.warning ? undefined : suffix,
|
|
179
|
-
warning: result.warning,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
return result;
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
// Invalid suffix
|
|
186
|
-
const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;
|
|
187
|
-
if (!allowFallback) {
|
|
188
|
-
// In strict mode (CLI --model parsing), treat it as part of the model id and fail.
|
|
189
|
-
// This avoids accidentally resolving to a different model.
|
|
190
|
-
return { model: undefined, thinkingLevel: undefined, warning: undefined };
|
|
191
|
-
}
|
|
192
|
-
// Scope mode: recurse on prefix and warn
|
|
193
|
-
const result = parseModelPattern(prefix, availableModels, options);
|
|
194
|
-
if (result.model) {
|
|
195
|
-
return {
|
|
196
|
-
model: result.model,
|
|
197
|
-
thinkingLevel: undefined,
|
|
198
|
-
warning: `Invalid thinking level "${suffix}" in pattern "${pattern}". Using default instead.`,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
return result;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
225
|
export function resolveModelScopeFromModels(patterns, models) {
|
|
205
|
-
const availableModels = [...models];
|
|
206
226
|
const scopedModels = [];
|
|
207
227
|
const diagnostics = [];
|
|
228
|
+
const add = (model, thinkingLevel) => {
|
|
229
|
+
if (!scopedModels.some((scoped) => modelsAreEqual(scoped.model, model))) {
|
|
230
|
+
scopedModels.push({ model, thinkingLevel });
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const noMatch = (pattern) => diagnostics.push({ type: "warning", code: "no-match", message: `No models match pattern "${pattern}"`, pattern });
|
|
234
|
+
const ambiguous = (pattern, candidates) => {
|
|
235
|
+
const providers = [...new Set(candidates.map((m) => m.provider))].sort((a, b) => a.localeCompare(b));
|
|
236
|
+
diagnostics.push({
|
|
237
|
+
type: "warning",
|
|
238
|
+
code: "ambiguous",
|
|
239
|
+
message: `Pattern "${pattern}" matches several providers: ${providers.join(", ")}. Use provider/id to choose one.`,
|
|
240
|
+
pattern,
|
|
241
|
+
});
|
|
242
|
+
};
|
|
208
243
|
for (const pattern of patterns) {
|
|
209
244
|
// Check if pattern contains glob characters
|
|
210
245
|
if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
|
|
211
246
|
// Extract optional thinking level suffix (e.g., "provider/*:high")
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (exactMatch) {
|
|
224
|
-
if (!scopedModels.find((sm) => modelsAreEqual(sm.model, exactMatch))) {
|
|
225
|
-
scopedModels.push({ model: exactMatch, thinkingLevel });
|
|
226
|
-
}
|
|
227
|
-
continue;
|
|
247
|
+
const thinking = splitThinkingSuffix(pattern);
|
|
248
|
+
const globPattern = thinking?.base ?? pattern;
|
|
249
|
+
const thinkingLevel = thinking?.thinkingLevel;
|
|
250
|
+
// Ids such as `bracketed-model[1m]` are not globs
|
|
251
|
+
const exact = resolveModelReference({ text: globPattern, thinkingSuffix: "none", fuzzy: false }, models, () => true);
|
|
252
|
+
let matches = [];
|
|
253
|
+
if (exact.status === "resolved")
|
|
254
|
+
matches = [exact.model];
|
|
255
|
+
else if (exact.status === "ambiguous") {
|
|
256
|
+
matches = exact.candidates;
|
|
257
|
+
ambiguous(pattern, matches);
|
|
228
258
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
});
|
|
235
|
-
if (matchingModels.length === 0) {
|
|
236
|
-
diagnostics.push({
|
|
237
|
-
type: "warning",
|
|
238
|
-
code: "no-match",
|
|
239
|
-
message: `No models match pattern "${pattern}"`,
|
|
240
|
-
pattern,
|
|
241
|
-
});
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
for (const model of matchingModels) {
|
|
245
|
-
if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {
|
|
246
|
-
scopedModels.push({ model, thinkingLevel });
|
|
247
|
-
}
|
|
259
|
+
else {
|
|
260
|
+
// Match against "provider/modelId" format OR just model ID
|
|
261
|
+
// This allows "*sonnet*" to match without requiring "anthropic/*sonnet*"
|
|
262
|
+
matches = models.filter((m) => minimatch(`${m.provider}/${m.id}`, globPattern, { nocase: true }) ||
|
|
263
|
+
minimatch(m.id, globPattern, { nocase: true }));
|
|
248
264
|
}
|
|
265
|
+
if (matches.length === 0)
|
|
266
|
+
noMatch(pattern);
|
|
267
|
+
for (const model of matches)
|
|
268
|
+
add(model, thinkingLevel);
|
|
249
269
|
continue;
|
|
250
270
|
}
|
|
251
|
-
const {
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
if (!model) {
|
|
256
|
-
diagnostics.push({
|
|
257
|
-
type: "warning",
|
|
258
|
-
code: "no-match",
|
|
259
|
-
message: `No models match pattern "${pattern}"`,
|
|
260
|
-
pattern,
|
|
261
|
-
});
|
|
271
|
+
const result = resolveModelReference({ text: pattern, thinkingSuffix: "lenient", fuzzy: true }, models, () => true);
|
|
272
|
+
if (result.status === "not-found") {
|
|
273
|
+
noMatch(pattern);
|
|
262
274
|
continue;
|
|
263
275
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
scopedModels.push({ model, thinkingLevel });
|
|
276
|
+
if (result.warning) {
|
|
277
|
+
diagnostics.push({ type: "warning", code: "invalid-thinking-level", message: result.warning, pattern });
|
|
267
278
|
}
|
|
279
|
+
if (result.status === "ambiguous")
|
|
280
|
+
ambiguous(pattern, result.candidates);
|
|
281
|
+
const matches = result.status === "resolved" ? [result.model] : result.candidates;
|
|
282
|
+
for (const model of matches)
|
|
283
|
+
add(model, result.thinkingLevel);
|
|
268
284
|
}
|
|
269
285
|
return { scopedModels, diagnostics };
|
|
270
286
|
}
|
|
@@ -290,26 +306,23 @@ export async function resolveModelScope(patterns, modelRuntime, options) {
|
|
|
290
306
|
* return a thinking level from "<pattern>:<thinking>" so the caller can apply it.
|
|
291
307
|
*/
|
|
292
308
|
export function resolveCliModel(options) {
|
|
293
|
-
const { cliProvider, cliModel, cliThinking, modelRuntime } = options;
|
|
309
|
+
const { cliProvider, cliModel, cliThinking, inlineApiKey, modelRuntime } = options;
|
|
294
310
|
if (!cliModel) {
|
|
295
311
|
return { model: undefined, warning: undefined, error: undefined };
|
|
296
312
|
}
|
|
297
313
|
// Important: use *all* models here, not just models with pre-configured auth.
|
|
298
314
|
// This allows "--api-key" to be used for first-time setup.
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
315
|
+
const models = [...modelRuntime.getModels()];
|
|
316
|
+
if (models.length === 0) {
|
|
301
317
|
return {
|
|
302
318
|
model: undefined,
|
|
303
319
|
warning: undefined,
|
|
304
320
|
error: "No models available. Check your installation or add models to models.json.",
|
|
305
321
|
};
|
|
306
322
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
providerMap.set(m.provider.toLowerCase(), m.provider);
|
|
311
|
-
}
|
|
312
|
-
let provider = cliProvider ? providerMap.get(cliProvider.toLowerCase()) : undefined;
|
|
323
|
+
const provider = cliProvider
|
|
324
|
+
? models.find((m) => m.provider.toLowerCase() === cliProvider.toLowerCase())?.provider
|
|
325
|
+
: undefined;
|
|
313
326
|
if (cliProvider && !provider) {
|
|
314
327
|
return {
|
|
315
328
|
model: undefined,
|
|
@@ -317,179 +330,61 @@ export function resolveCliModel(options) {
|
|
|
317
330
|
error: `Unknown provider "${cliProvider}". Use --list-models to see available providers/models.`,
|
|
318
331
|
};
|
|
319
332
|
}
|
|
320
|
-
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
pattern = cliModel.substring(slashIndex + 1);
|
|
335
|
-
inferredProvider = true;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
// If no provider was inferred from the slash, try exact matches without provider inference.
|
|
340
|
-
// This handles models whose IDs naturally contain slashes (e.g. OpenRouter-style IDs).
|
|
341
|
-
// Bare exact IDs can exist in multiple providers, so do not choose by catalog order.
|
|
342
|
-
// Prefer the sole authenticated provider when there is one; otherwise require an
|
|
343
|
-
// explicit provider to avoid silently selecting an unusable provider.
|
|
344
|
-
if (!provider) {
|
|
345
|
-
const lower = cliModel.toLowerCase();
|
|
346
|
-
const exactMatches = availableModels.filter((m) => m.id.toLowerCase() === lower || `${m.provider}/${m.id}`.toLowerCase() === lower);
|
|
347
|
-
if (exactMatches.length === 1) {
|
|
348
|
-
return { model: exactMatches[0], warning: undefined, thinkingLevel: undefined, error: undefined };
|
|
349
|
-
}
|
|
350
|
-
if (exactMatches.length > 1) {
|
|
351
|
-
const authenticatedExactMatches = exactMatches.filter((m) => modelRuntime.hasConfiguredAuth(m.provider));
|
|
352
|
-
if (authenticatedExactMatches.length === 1) {
|
|
353
|
-
return {
|
|
354
|
-
model: authenticatedExactMatches[0],
|
|
355
|
-
warning: undefined,
|
|
356
|
-
thinkingLevel: undefined,
|
|
357
|
-
error: undefined,
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
const matches = exactMatches
|
|
361
|
-
.map((m) => `${m.provider}/${m.id}`)
|
|
362
|
-
.sort((a, b) => a.localeCompare(b))
|
|
363
|
-
.join(", ");
|
|
364
|
-
const authHint = authenticatedExactMatches.length === 0
|
|
333
|
+
const result = resolveModelReference({ text: cliModel, provider, thinkingSuffix: "strict", fuzzy: true }, models,
|
|
334
|
+
// An inline key goes to whichever provider is selected, so stored credentials must not choose it
|
|
335
|
+
inlineApiKey ? () => false : (providerId) => modelRuntime.hasConfiguredAuth(providerId));
|
|
336
|
+
if (result.status === "resolved") {
|
|
337
|
+
return { model: result.model, thinkingLevel: result.thinkingLevel, warning: result.warning, error: undefined };
|
|
338
|
+
}
|
|
339
|
+
if (result.status === "ambiguous") {
|
|
340
|
+
const matches = result.candidates
|
|
341
|
+
.map((m) => `${m.provider}/${m.id}`)
|
|
342
|
+
.sort((a, b) => a.localeCompare(b))
|
|
343
|
+
.join(", ");
|
|
344
|
+
const authHint = inlineApiKey
|
|
345
|
+
? "--api-key does not choose a provider."
|
|
346
|
+
: result.usableCount === 0
|
|
365
347
|
? "No matching provider is authenticated."
|
|
366
348
|
: "More than one matching provider is authenticated.";
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
if (cliProvider && provider) {
|
|
376
|
-
// If both were provided, tolerate --model <provider>/<pattern> by stripping the provider prefix
|
|
377
|
-
const prefix = `${provider}/`;
|
|
378
|
-
if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
379
|
-
pattern = cliModel.substring(prefix.length);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const candidates = provider ? availableModels.filter((m) => m.provider === provider) : availableModels;
|
|
383
|
-
const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, {
|
|
384
|
-
allowInvalidThinkingLevelFallback: false,
|
|
385
|
-
});
|
|
386
|
-
if (model) {
|
|
387
|
-
// If provider inference matched an unauthenticated provider/model pair, prefer
|
|
388
|
-
// one exact raw model-id match that is authenticated. This keeps
|
|
389
|
-
// "provider/model" syntax preferred when usable, but handles models whose
|
|
390
|
-
// literal id starts with a known provider name (for example
|
|
391
|
-
// commandcode model id "xiaomi/mimo-v2.5-pro").
|
|
392
|
-
if (inferredProvider) {
|
|
393
|
-
const rawExactMatches = availableModels.filter((m) => m.id.toLowerCase() === cliModel.toLowerCase() && !modelsAreEqual(m, model));
|
|
394
|
-
if (rawExactMatches.length > 0 && !modelRuntime.hasConfiguredAuth(model.provider)) {
|
|
395
|
-
const authenticatedRawMatches = rawExactMatches.filter((m) => modelRuntime.hasConfiguredAuth(m.provider));
|
|
396
|
-
if (authenticatedRawMatches.length === 1) {
|
|
397
|
-
return {
|
|
398
|
-
model: authenticatedRawMatches[0],
|
|
399
|
-
thinkingLevel: undefined,
|
|
400
|
-
warning: undefined,
|
|
401
|
-
error: undefined,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return { model, thinkingLevel, warning, error: undefined };
|
|
407
|
-
}
|
|
408
|
-
// If we inferred a provider from the slash but found no match within that provider,
|
|
409
|
-
// fall back to matching the full input as a raw model id across all models.
|
|
410
|
-
// This handles OpenRouter-style IDs like "openai/gpt-4o:extended" where "openai"
|
|
411
|
-
// looks like a provider but the full string is actually a model id on openrouter.
|
|
412
|
-
if (inferredProvider) {
|
|
413
|
-
const lower = cliModel.toLowerCase();
|
|
414
|
-
const exact = availableModels.find((m) => m.id.toLowerCase() === lower || `${m.provider}/${m.id}`.toLowerCase() === lower);
|
|
415
|
-
if (exact) {
|
|
416
|
-
return { model: exact, warning: undefined, thinkingLevel: undefined, error: undefined };
|
|
417
|
-
}
|
|
418
|
-
// Also try parseModelPattern on the full input against all models
|
|
419
|
-
const fallback = parseModelPattern(cliModel, availableModels, {
|
|
420
|
-
allowInvalidThinkingLevelFallback: false,
|
|
421
|
-
});
|
|
422
|
-
if (fallback.model) {
|
|
423
|
-
return {
|
|
424
|
-
model: fallback.model,
|
|
425
|
-
thinkingLevel: fallback.thinkingLevel,
|
|
426
|
-
warning: fallback.warning,
|
|
427
|
-
error: undefined,
|
|
428
|
-
};
|
|
429
|
-
}
|
|
349
|
+
return {
|
|
350
|
+
model: undefined,
|
|
351
|
+
warning: undefined,
|
|
352
|
+
error: `Model "${cliModel}" is ambiguous across providers: ${matches}. ${authHint} Use --provider or provider/model.`,
|
|
353
|
+
};
|
|
430
354
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const lastColon = pattern.lastIndexOf(":");
|
|
439
|
-
if (lastColon !== -1) {
|
|
440
|
-
const suffix = pattern.substring(lastColon + 1);
|
|
441
|
-
if (isValidThinkingLevel(suffix)) {
|
|
442
|
-
fallbackPattern = pattern.substring(0, lastColon);
|
|
443
|
-
fallbackThinking = suffix;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
const fallbackModel = buildFallbackModel(provider, fallbackPattern, availableModels);
|
|
448
|
-
if (fallbackModel) {
|
|
449
|
-
const requestedThinking = cliThinking ?? fallbackThinking;
|
|
450
|
-
const model = requestedThinking && requestedThinking !== "off" ? { ...fallbackModel, reasoning: true } : fallbackModel;
|
|
451
|
-
const fallbackWarning = warning
|
|
452
|
-
? `${warning} Model "${fallbackPattern}" not found for provider "${provider}". Using custom model id.`
|
|
453
|
-
: `Model "${fallbackPattern}" not found for provider "${provider}". Using custom model id.`;
|
|
454
|
-
return { model, thinkingLevel: fallbackThinking, warning: fallbackWarning, error: undefined };
|
|
455
|
-
}
|
|
355
|
+
const { target } = result;
|
|
356
|
+
if (!target) {
|
|
357
|
+
return {
|
|
358
|
+
model: undefined,
|
|
359
|
+
warning: undefined,
|
|
360
|
+
error: `Model "${cliModel}" not found. Use --list-models to see available models.`,
|
|
361
|
+
};
|
|
456
362
|
}
|
|
457
|
-
|
|
363
|
+
// Use the thinking level suffix only when --thinking is not explicitly provided; otherwise it
|
|
364
|
+
// stays part of the custom model id.
|
|
365
|
+
// e.g. "zai-org/GLM-5.1-FP8:high" → modelId="zai-org/GLM-5.1-FP8", fallbackThinking="high"
|
|
366
|
+
const modelId = cliThinking ? target.modelText : target.modelId;
|
|
367
|
+
const fallbackThinking = cliThinking ? undefined : target.thinkingLevel;
|
|
368
|
+
const fallbackModel = buildFallbackModel(target.providerModels, modelId);
|
|
369
|
+
const requestedThinking = cliThinking ?? fallbackThinking;
|
|
458
370
|
return {
|
|
459
|
-
model:
|
|
460
|
-
thinkingLevel:
|
|
461
|
-
warning
|
|
462
|
-
error:
|
|
371
|
+
model: requestedThinking && requestedThinking !== "off" ? { ...fallbackModel, reasoning: true } : fallbackModel,
|
|
372
|
+
thinkingLevel: fallbackThinking,
|
|
373
|
+
warning: `Model "${modelId}" not found for provider "${target.provider}". Using custom model id.`,
|
|
374
|
+
error: undefined,
|
|
463
375
|
};
|
|
464
376
|
}
|
|
465
377
|
/**
|
|
466
378
|
* Find the initial model to use based on priority:
|
|
467
|
-
* 1.
|
|
468
|
-
* 2.
|
|
469
|
-
* 3.
|
|
470
|
-
* 4. Saved default from settings
|
|
471
|
-
* 5. First available model with valid API key
|
|
379
|
+
* 1. First model from scoped models (if not continuing/resuming)
|
|
380
|
+
* 2. Saved default from settings
|
|
381
|
+
* 3. First available model with valid API key
|
|
472
382
|
*/
|
|
473
383
|
export async function findInitialModel(options) {
|
|
474
|
-
const {
|
|
384
|
+
const { scopedModels, isContinuing, defaultProvider, defaultModelId, defaultThinkingLevel, modelThinkingLevels, modelRuntime, } = options;
|
|
475
385
|
let model;
|
|
476
386
|
let thinkingLevel = DEFAULT_THINKING_LEVEL;
|
|
477
|
-
// 1.
|
|
478
|
-
if (cliProvider && cliModel) {
|
|
479
|
-
const resolved = resolveCliModel({
|
|
480
|
-
cliProvider,
|
|
481
|
-
cliModel,
|
|
482
|
-
modelRuntime,
|
|
483
|
-
});
|
|
484
|
-
if (resolved.error) {
|
|
485
|
-
console.error(chalk.red(resolved.error));
|
|
486
|
-
process.exit(1);
|
|
487
|
-
}
|
|
488
|
-
if (resolved.model) {
|
|
489
|
-
return { model: resolved.model, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined };
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
// 2. Use first model from scoped models (skip if continuing/resuming)
|
|
387
|
+
// 1. Use first model from scoped models (skip if continuing/resuming)
|
|
493
388
|
if (scopedModels.length > 0 && !isContinuing) {
|
|
494
389
|
const scopedModel = scopedModels[0];
|
|
495
390
|
const perModel = modelThinkingLevels?.[`${scopedModel.model.provider}/${scopedModel.model.id}`];
|
|
@@ -499,7 +394,7 @@ export async function findInitialModel(options) {
|
|
|
499
394
|
fallbackMessage: undefined,
|
|
500
395
|
};
|
|
501
396
|
}
|
|
502
|
-
//
|
|
397
|
+
// 2. Try saved default from settings if auth is configured.
|
|
503
398
|
if (defaultProvider && defaultModelId) {
|
|
504
399
|
const found = modelRuntime.getModel(defaultProvider, defaultModelId);
|
|
505
400
|
if (found && modelRuntime.hasConfiguredAuth(found.provider)) {
|
|
@@ -514,7 +409,7 @@ export async function findInitialModel(options) {
|
|
|
514
409
|
return { model, thinkingLevel, fallbackMessage: undefined };
|
|
515
410
|
}
|
|
516
411
|
}
|
|
517
|
-
//
|
|
412
|
+
// 3. Try first available model with valid API key
|
|
518
413
|
const availableModels = [...modelRuntime.getAvailableSnapshot()];
|
|
519
414
|
if (availableModels.length > 0) {
|
|
520
415
|
// Try to find a default model from known providers
|
|
@@ -528,7 +423,7 @@ export async function findInitialModel(options) {
|
|
|
528
423
|
// If no default found, use first available
|
|
529
424
|
return { model: availableModels[0], thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined };
|
|
530
425
|
}
|
|
531
|
-
//
|
|
426
|
+
// 4. No model found
|
|
532
427
|
return { model: undefined, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined };
|
|
533
428
|
}
|
|
534
429
|
/**
|