@oh-my-pi/pi-coding-agent 11.0.3 → 11.2.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/CHANGELOG.md +199 -49
- package/README.md +1 -1
- package/docs/config-usage.md +3 -4
- package/docs/sdk.md +6 -5
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +1 -1
- package/package.json +19 -11
- package/src/cli/args.ts +11 -94
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/oclif-help.ts +26 -0
- package/src/cli/web-search-cli.ts +148 -0
- package/src/cli.ts +8 -2
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/index/index.ts +136 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/plugin.ts +70 -0
- package/src/commands/setup.ts +39 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +50 -0
- package/src/commit/agentic/index.ts +3 -2
- package/src/commit/agentic/tools/analyze-file.ts +1 -3
- package/src/commit/git/errors.ts +4 -6
- package/src/commit/pipeline.ts +3 -2
- package/src/config/keybindings.ts +1 -3
- package/src/config/model-registry.ts +89 -162
- package/src/config/settings-schema.ts +10 -0
- package/src/config.ts +202 -132
- package/src/exa/mcp-client.ts +8 -41
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/extensions/loader.ts +7 -10
- package/src/extensibility/extensions/runner.ts +5 -15
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/runner.ts +6 -9
- package/src/index.ts +0 -1
- package/src/ipy/kernel.ts +10 -22
- package/src/lsp/clients/biome-client.ts +4 -7
- package/src/lsp/clients/lsp-linter-client.ts +4 -6
- package/src/lsp/index.ts +5 -4
- package/src/lsp/utils.ts +18 -0
- package/src/main.ts +86 -181
- package/src/mcp/json-rpc.ts +2 -2
- package/src/mcp/transports/http.ts +12 -49
- package/src/modes/components/armin.ts +1 -3
- package/src/modes/components/assistant-message.ts +4 -4
- package/src/modes/components/bash-execution.ts +5 -3
- package/src/modes/components/branch-summary-message.ts +1 -3
- package/src/modes/components/compaction-summary-message.ts +1 -3
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/extensions/extension-dashboard.ts +10 -16
- package/src/modes/components/extensions/extension-list.ts +5 -5
- package/src/modes/components/footer.ts +1 -4
- package/src/modes/components/hook-editor.ts +7 -32
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/plugin-settings.ts +16 -20
- package/src/modes/components/python-execution.ts +5 -5
- package/src/modes/components/session-selector.ts +6 -7
- package/src/modes/components/settings-defs.ts +49 -40
- package/src/modes/components/settings-selector.ts +8 -17
- package/src/modes/components/skill-message.ts +1 -3
- package/src/modes/components/status-line-segment-editor.ts +1 -3
- package/src/modes/components/status-line.ts +1 -3
- package/src/modes/components/todo-reminder.ts +5 -7
- package/src/modes/components/tree-selector.ts +10 -12
- package/src/modes/components/ttsr-notification.ts +1 -3
- package/src/modes/components/user-message-selector.ts +2 -4
- package/src/modes/components/welcome.ts +6 -18
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +7 -34
- package/src/modes/controllers/selector-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +27 -1
- package/src/modes/rpc/rpc-client.ts +2 -5
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/theme/theme.ts +2 -6
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +6 -1
- package/src/patch/index.ts +1 -4
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/frontmatter.md +2 -1
- package/src/prompts/agents/init.md +1 -0
- package/src/prompts/agents/plan.md +1 -0
- package/src/prompts/agents/reviewer.md +1 -0
- package/src/prompts/system/subagent-submit-reminder.md +2 -0
- package/src/prompts/system/subagent-system-prompt.md +2 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/system-prompt.md +5 -3
- package/src/prompts/system/web-search.md +6 -4
- package/src/prompts/tools/task.md +216 -163
- package/src/sdk.ts +11 -110
- package/src/session/agent-session.ts +117 -83
- package/src/session/auth-storage.ts +10 -51
- package/src/session/messages.ts +17 -3
- package/src/session/session-manager.ts +30 -30
- package/src/session/streaming-output.ts +1 -1
- package/src/ssh/ssh-executor.ts +6 -3
- package/src/task/agents.ts +2 -0
- package/src/task/discovery.ts +1 -1
- package/src/task/executor.ts +5 -10
- package/src/task/index.ts +43 -23
- package/src/task/render.ts +67 -64
- package/src/task/template.ts +17 -34
- package/src/task/types.ts +49 -22
- package/src/tools/ask.ts +1 -3
- package/src/tools/bash.ts +1 -4
- package/src/tools/browser.ts +5 -7
- package/src/tools/exit-plan-mode.ts +1 -4
- package/src/tools/fetch.ts +1 -3
- package/src/tools/find.ts +4 -3
- package/src/tools/gemini-image.ts +24 -55
- package/src/tools/grep.ts +4 -4
- package/src/tools/index.ts +12 -14
- package/src/tools/notebook.ts +1 -5
- package/src/tools/python.ts +4 -3
- package/src/tools/read.ts +2 -4
- package/src/tools/render-utils.ts +23 -0
- package/src/tools/ssh.ts +8 -12
- package/src/tools/todo-write.ts +1 -4
- package/src/tools/tool-errors.ts +1 -4
- package/src/tools/write.ts +1 -3
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-mentions.ts +39 -1
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +4 -4
- package/src/web/search/auth.ts +3 -33
- package/src/web/search/index.ts +73 -139
- package/src/web/search/provider.ts +58 -0
- package/src/web/search/providers/anthropic.ts +53 -14
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/codex.ts +38 -16
- package/src/web/search/providers/exa.ts +30 -6
- package/src/web/search/providers/gemini.ts +56 -20
- package/src/web/search/providers/jina.ts +28 -5
- package/src/web/search/providers/perplexity.ts +103 -36
- package/src/web/search/render.ts +84 -74
- package/src/web/search/types.ts +285 -59
- package/src/migrations.ts +0 -175
- package/src/session/storage-migration.ts +0 -173
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model registry - manages built-in and custom models, provides API key resolution.
|
|
3
|
-
*/
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
1
|
import {
|
|
7
2
|
type Api,
|
|
8
3
|
getGitHubCopilotBaseUrl,
|
|
@@ -11,10 +6,9 @@ import {
|
|
|
11
6
|
type Model,
|
|
12
7
|
normalizeDomain,
|
|
13
8
|
} from "@oh-my-pi/pi-ai";
|
|
14
|
-
import {
|
|
9
|
+
import { type ConfigError, ConfigFile } from "@oh-my-pi/pi-coding-agent/config";
|
|
15
10
|
import { type Static, Type } from "@sinclair/typebox";
|
|
16
11
|
import AjvModule from "ajv";
|
|
17
|
-
import { YAML } from "bun";
|
|
18
12
|
import type { ThemeColor } from "../modes/theme/theme";
|
|
19
13
|
import type { AuthStorage } from "../session/auth-storage";
|
|
20
14
|
|
|
@@ -36,7 +30,7 @@ export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
|
|
|
36
30
|
|
|
37
31
|
export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "plan", "commit"];
|
|
38
32
|
|
|
39
|
-
const
|
|
33
|
+
const _Ajv = (AjvModule as any).default || AjvModule;
|
|
40
34
|
|
|
41
35
|
const OpenRouterRoutingSchema = Type.Object({
|
|
42
36
|
only: Type.Optional(Type.Array(Type.String())),
|
|
@@ -113,6 +107,50 @@ const ModelsConfigSchema = Type.Object({
|
|
|
113
107
|
|
|
114
108
|
type ModelsConfig = Static<typeof ModelsConfigSchema>;
|
|
115
109
|
|
|
110
|
+
export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsConfigSchema).withValidation(
|
|
111
|
+
"models",
|
|
112
|
+
config => {
|
|
113
|
+
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
114
|
+
const hasProviderApi = !!providerConfig.api;
|
|
115
|
+
const models = providerConfig.models ?? [];
|
|
116
|
+
|
|
117
|
+
if (models.length === 0) {
|
|
118
|
+
// Override-only config: just needs baseUrl (to override built-in)
|
|
119
|
+
if (!providerConfig.baseUrl) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Provider ${providerName}: must specify either "baseUrl" (for override) or "models" (for replacement).`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
// Full replacement: needs baseUrl and apiKey
|
|
126
|
+
if (!providerConfig.baseUrl) {
|
|
127
|
+
throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
|
|
128
|
+
}
|
|
129
|
+
if (!providerConfig.apiKey) {
|
|
130
|
+
throw new Error(`Provider ${providerName}: "apiKey" is required when defining custom models.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const modelDef of models) {
|
|
135
|
+
const hasModelApi = !!modelDef.api;
|
|
136
|
+
|
|
137
|
+
if (!hasProviderApi && !hasModelApi) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!modelDef.id) throw new Error(`Provider ${providerName}: model missing "id"`);
|
|
144
|
+
if (!modelDef.name) throw new Error(`Provider ${providerName}: model missing "name"`);
|
|
145
|
+
if (modelDef.contextWindow <= 0)
|
|
146
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
|
|
147
|
+
if (modelDef.maxTokens <= 0)
|
|
148
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
116
154
|
/** Provider override config (baseUrl, headers, apiKey) without custom models */
|
|
117
155
|
interface ProviderOverride {
|
|
118
156
|
baseUrl?: string;
|
|
@@ -126,25 +164,19 @@ interface ProviderOverride {
|
|
|
126
164
|
export interface SerializedModelRegistry {
|
|
127
165
|
models: Model<Api>[];
|
|
128
166
|
customProviderApiKeys?: Record<string, string>;
|
|
129
|
-
loadError?: string;
|
|
130
167
|
}
|
|
131
168
|
|
|
132
169
|
/** Result of loading custom models from models.json */
|
|
133
170
|
interface CustomModelsResult {
|
|
134
|
-
models
|
|
171
|
+
models?: Model<Api>[];
|
|
135
172
|
/** Providers with custom models (full replacement) */
|
|
136
|
-
replacedProviders
|
|
173
|
+
replacedProviders?: Set<string>;
|
|
137
174
|
/** Providers with only baseUrl/headers override (no custom models) */
|
|
138
|
-
overrides
|
|
139
|
-
error
|
|
140
|
-
/** Whether the file was found (true) or didn't exist (false) */
|
|
175
|
+
overrides?: Map<string, ProviderOverride>;
|
|
176
|
+
error?: ConfigError;
|
|
141
177
|
found: boolean;
|
|
142
178
|
}
|
|
143
179
|
|
|
144
|
-
function emptyCustomModelsResult(error?: string): CustomModelsResult {
|
|
145
|
-
return { models: [], replacedProviders: new Set(), overrides: new Map(), error, found: false };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
180
|
/**
|
|
149
181
|
* Resolve an API key config value to an actual key.
|
|
150
182
|
* Checks environment variable first, then treats as literal.
|
|
@@ -161,18 +193,17 @@ function resolveApiKeyConfig(keyConfig: string): string | undefined {
|
|
|
161
193
|
export class ModelRegistry {
|
|
162
194
|
private models: Model<Api>[] = [];
|
|
163
195
|
private customProviderApiKeys: Map<string, string> = new Map();
|
|
164
|
-
private
|
|
196
|
+
private configError: ConfigError | undefined = undefined;
|
|
197
|
+
private modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
165
198
|
|
|
166
199
|
/**
|
|
167
200
|
* @param authStorage - Auth storage for API key resolution
|
|
168
|
-
* @param modelsJsonPath - Primary path for models.json
|
|
169
|
-
* @param fallbackPaths - Additional paths to check (legacy support)
|
|
170
201
|
*/
|
|
171
202
|
constructor(
|
|
172
203
|
readonly authStorage: AuthStorage,
|
|
173
|
-
|
|
174
|
-
private fallbackPaths: string[] = [],
|
|
204
|
+
modelsPath?: string,
|
|
175
205
|
) {
|
|
206
|
+
this.modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
|
|
176
207
|
// Set up fallback resolver for custom provider API keys
|
|
177
208
|
this.authStorage.setFallbackResolver(provider => {
|
|
178
209
|
const keyConfig = this.customProviderApiKeys.get(provider);
|
|
@@ -194,7 +225,6 @@ export class ModelRegistry {
|
|
|
194
225
|
(instance as any).authStorage = authStorage;
|
|
195
226
|
instance.models = data.models;
|
|
196
227
|
instance.customProviderApiKeys = new Map(Object.entries(data.customProviderApiKeys ?? {}));
|
|
197
|
-
instance.loadError = data.loadError;
|
|
198
228
|
|
|
199
229
|
authStorage.setFallbackResolver(provider => {
|
|
200
230
|
const keyConfig = instance.customProviderApiKeys.get(provider);
|
|
@@ -218,7 +248,6 @@ export class ModelRegistry {
|
|
|
218
248
|
return {
|
|
219
249
|
models: this.models,
|
|
220
250
|
customProviderApiKeys: Object.keys(customProviderApiKeys).length > 0 ? customProviderApiKeys : undefined,
|
|
221
|
-
loadError: this.loadError,
|
|
222
251
|
};
|
|
223
252
|
}
|
|
224
253
|
|
|
@@ -226,45 +255,28 @@ export class ModelRegistry {
|
|
|
226
255
|
* Reload models from disk (built-in + custom from models.json).
|
|
227
256
|
*/
|
|
228
257
|
refresh(): void {
|
|
258
|
+
this.modelsConfigFile.invalidate();
|
|
229
259
|
this.customProviderApiKeys.clear();
|
|
230
|
-
this.
|
|
260
|
+
this.configError = undefined;
|
|
231
261
|
this.loadModels();
|
|
232
262
|
}
|
|
233
263
|
|
|
234
264
|
/**
|
|
235
265
|
* Get any error from loading models.json (undefined if no error).
|
|
236
266
|
*/
|
|
237
|
-
getError():
|
|
238
|
-
return this.
|
|
267
|
+
getError(): ConfigError | undefined {
|
|
268
|
+
return this.configError;
|
|
239
269
|
}
|
|
240
270
|
|
|
241
271
|
private loadModels() {
|
|
242
272
|
// Load custom models from models.json first (to know which providers to skip/override)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
for (const modelsPath of pathsToCheck) {
|
|
253
|
-
const result = this.loadCustomModels(modelsPath);
|
|
254
|
-
if (!result.found) {
|
|
255
|
-
continue; // File doesn't exist, try next path
|
|
256
|
-
}
|
|
257
|
-
logger.debug("ModelRegistry.loadModels loading", { path: modelsPath });
|
|
258
|
-
if (result.error) {
|
|
259
|
-
this.loadError = result.error;
|
|
260
|
-
// Keep built-in models even if custom models failed to load
|
|
261
|
-
} else {
|
|
262
|
-
customModels = result.models;
|
|
263
|
-
replacedProviders = result.replacedProviders;
|
|
264
|
-
overrides = result.overrides;
|
|
265
|
-
}
|
|
266
|
-
break; // Use first existing file
|
|
267
|
-
}
|
|
273
|
+
const {
|
|
274
|
+
models: customModels = [],
|
|
275
|
+
replacedProviders = new Set(),
|
|
276
|
+
overrides = new Map(),
|
|
277
|
+
error: configError,
|
|
278
|
+
} = this.loadCustomModels();
|
|
279
|
+
this.configError = configError;
|
|
268
280
|
|
|
269
281
|
const builtInModels = this.loadBuiltInModels(replacedProviders, overrides);
|
|
270
282
|
const combined = [...builtInModels, ...customModels];
|
|
@@ -300,123 +312,38 @@ export class ModelRegistry {
|
|
|
300
312
|
});
|
|
301
313
|
}
|
|
302
314
|
|
|
303
|
-
private loadCustomModels(
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
content = fs.readFileSync(modelsPath, "utf-8");
|
|
307
|
-
} catch (error) {
|
|
308
|
-
if (isEnoent(error)) {
|
|
309
|
-
return emptyCustomModelsResult();
|
|
310
|
-
}
|
|
311
|
-
return {
|
|
312
|
-
...emptyCustomModelsResult(
|
|
313
|
-
`Failed to load models config: ${error instanceof Error ? error.message : error}\n\nFile: ${modelsPath}`,
|
|
314
|
-
),
|
|
315
|
-
found: true,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
const ext = path.extname(modelsPath).toLowerCase();
|
|
321
|
-
let config: ModelsConfig;
|
|
322
|
-
|
|
323
|
-
if (ext === ".yaml" || ext === ".yml") {
|
|
324
|
-
config = YAML.parse(content) as ModelsConfig;
|
|
325
|
-
} else {
|
|
326
|
-
config = JSON.parse(content) as ModelsConfig;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Validate schema
|
|
330
|
-
const ajv = new Ajv();
|
|
331
|
-
const validate = ajv.compile(ModelsConfigSchema);
|
|
332
|
-
if (!validate(config)) {
|
|
333
|
-
const errors =
|
|
334
|
-
validate.errors?.map((e: any) => ` - ${e.instancePath || "root"}: ${e.message}`).join("\n") ||
|
|
335
|
-
"Unknown schema error";
|
|
336
|
-
return emptyCustomModelsResult(`Invalid models config schema:\n${errors}\n\nFile: ${modelsPath}`);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Additional validation
|
|
340
|
-
this.validateConfig(config);
|
|
341
|
-
|
|
342
|
-
// Separate providers into "full replacement" (has models) vs "override-only" (no models)
|
|
343
|
-
const replacedProviders = new Set<string>();
|
|
344
|
-
const overrides = new Map<string, ProviderOverride>();
|
|
345
|
-
|
|
346
|
-
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
347
|
-
if (providerConfig.models && providerConfig.models.length > 0) {
|
|
348
|
-
// Has custom models -> full replacement
|
|
349
|
-
replacedProviders.add(providerName);
|
|
350
|
-
} else {
|
|
351
|
-
// No models -> just override baseUrl/headers on built-in
|
|
352
|
-
overrides.set(providerName, {
|
|
353
|
-
baseUrl: providerConfig.baseUrl,
|
|
354
|
-
headers: providerConfig.headers,
|
|
355
|
-
apiKey: providerConfig.apiKey,
|
|
356
|
-
});
|
|
357
|
-
// Store API key for fallback resolver
|
|
358
|
-
if (providerConfig.apiKey) {
|
|
359
|
-
this.customProviderApiKeys.set(providerName, providerConfig.apiKey);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
315
|
+
private loadCustomModels(): CustomModelsResult {
|
|
316
|
+
const { value, error, status } = this.modelsConfigFile.tryLoad();
|
|
363
317
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
...emptyCustomModelsResult(`Failed to parse models config: ${error.message}\n\nFile: ${modelsPath}`),
|
|
369
|
-
found: true,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
return {
|
|
373
|
-
...emptyCustomModelsResult(
|
|
374
|
-
`Failed to load models config: ${error instanceof Error ? error.message : error}\n\nFile: ${modelsPath}`,
|
|
375
|
-
),
|
|
376
|
-
found: true,
|
|
377
|
-
};
|
|
318
|
+
if (status === "error") {
|
|
319
|
+
return { models: [], replacedProviders: new Set(), overrides: new Map(), error, found: true };
|
|
320
|
+
} else if (status === "not-found") {
|
|
321
|
+
return { models: [], replacedProviders: new Set(), overrides: new Map(), found: false };
|
|
378
322
|
}
|
|
379
|
-
}
|
|
380
323
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const models = providerConfig.models ?? [];
|
|
324
|
+
// Separate providers into "full replacement" (has models) vs "override-only" (no models)
|
|
325
|
+
const replacedProviders = new Set<string>();
|
|
326
|
+
const overrides = new Map<string, ProviderOverride>();
|
|
385
327
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
`Provider ${providerName}: must specify either "baseUrl" (for override) or "models" (for replacement).`,
|
|
391
|
-
);
|
|
392
|
-
}
|
|
328
|
+
for (const [providerName, providerConfig] of Object.entries(value.providers)) {
|
|
329
|
+
if (providerConfig.models && providerConfig.models.length > 0) {
|
|
330
|
+
// Has custom models -> full replacement
|
|
331
|
+
replacedProviders.add(providerName);
|
|
393
332
|
} else {
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
for (const modelDef of models) {
|
|
404
|
-
const hasModelApi = !!modelDef.api;
|
|
405
|
-
|
|
406
|
-
if (!hasProviderApi && !hasModelApi) {
|
|
407
|
-
throw new Error(
|
|
408
|
-
`Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
|
|
409
|
-
);
|
|
333
|
+
// No models -> just override baseUrl/headers on built-in
|
|
334
|
+
overrides.set(providerName, {
|
|
335
|
+
baseUrl: providerConfig.baseUrl,
|
|
336
|
+
headers: providerConfig.headers,
|
|
337
|
+
apiKey: providerConfig.apiKey,
|
|
338
|
+
});
|
|
339
|
+
// Store API key for fallback resolver
|
|
340
|
+
if (providerConfig.apiKey) {
|
|
341
|
+
this.customProviderApiKeys.set(providerName, providerConfig.apiKey);
|
|
410
342
|
}
|
|
411
|
-
|
|
412
|
-
if (!modelDef.id) throw new Error(`Provider ${providerName}: model missing "id"`);
|
|
413
|
-
if (!modelDef.name) throw new Error(`Provider ${providerName}: model missing "name"`);
|
|
414
|
-
if (modelDef.contextWindow <= 0)
|
|
415
|
-
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
|
|
416
|
-
if (modelDef.maxTokens <= 0)
|
|
417
|
-
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);
|
|
418
343
|
}
|
|
419
344
|
}
|
|
345
|
+
|
|
346
|
+
return { models: this.parseModels(value), replacedProviders, overrides, found: true };
|
|
420
347
|
}
|
|
421
348
|
|
|
422
349
|
private parseModels(config: ModelsConfig): Model<Api>[] {
|
|
@@ -399,6 +399,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
399
399
|
// ─────────────────────────────────────────────────────────────────────────
|
|
400
400
|
// Task tool settings
|
|
401
401
|
// ─────────────────────────────────────────────────────────────────────────
|
|
402
|
+
"task.isolation.enabled": {
|
|
403
|
+
type: "boolean",
|
|
404
|
+
default: false,
|
|
405
|
+
ui: {
|
|
406
|
+
tab: "tools",
|
|
407
|
+
label: "Task isolation",
|
|
408
|
+
description: "Run subagents in isolated git worktrees",
|
|
409
|
+
submenu: true,
|
|
410
|
+
},
|
|
411
|
+
},
|
|
402
412
|
"task.maxConcurrency": {
|
|
403
413
|
type: "number",
|
|
404
414
|
default: 32,
|